Vellum.Static
0.1.0
dotnet add package Vellum.Static --version 0.1.0
NuGet\Install-Package Vellum.Static -Version 0.1.0
<PackageReference Include="Vellum.Static" Version="0.1.0" />
<PackageVersion Include="Vellum.Static" Version="0.1.0" />
<PackageReference Include="Vellum.Static" />
paket add Vellum.Static --version 0.1.0
#r "nuget: Vellum.Static, 0.1.0"
#:package Vellum.Static@0.1.0
#addin nuget:?package=Vellum.Static&version=0.1.0
#tool nuget:?package=Vellum.Static&version=0.1.0
Vellum
Vellum (n.) — fine parchment historically used for important documents, charters, and sealed letters. Evokes preservation, authenticity, and secrecy — the core values of envelope encryption.
Vellum is a provider-agnostic, production-grade envelope encryption library for .NET with DEK lifecycle management, key rotation, and multi-tenant support.
Status:
0.1.0— First stable drop. Pre-1.0 (API may evolve in 0.2.0+, breaking changes allowed per semver). Production use is supported but cloud KMS providers (Azure Key Vault, AWS KMS, GCP KMS) are planned for 0.3.0.
What is envelope encryption?
Envelope encryption is a pattern where:
- A Data Encryption Key (DEK) encrypts the actual payload (via AES-GCM).
- A Key Encryption Key (KEK), managed by a secure provider (Vault, KMS, HSM), wraps the DEK.
- Only the wrapped DEK is stored alongside the ciphertext.
- Decryption requires access to the KEK provider to unwrap the DEK.
This pattern lets you rotate keys at the KEK level without re-encrypting every payload, and ensures that a database dump alone is not sufficient to decrypt data.
Packages
| Package | Purpose |
|---|---|
Vellum.Abstractions |
Interfaces + value objects. Zero dependencies. |
Vellum.Core |
DekManager + PayloadEncryptor. AES-GCM via System.Security.Cryptography. |
Vellum.Vault |
HashiCorp Vault Transit KEK provider. |
Vellum.AzureKeyVault |
Azure Key Vault KEK provider (Phase 3). |
Vellum.AwsKms |
AWS KMS KEK provider (Phase 3). |
Vellum.GcpKms |
Google Cloud KMS KEK provider (Phase 3). |
Vellum.Static |
Static key provider for dev/test only — INSECURE. |
Vellum.EntityFrameworkCore |
EF Core-backed key store. |
Vellum.InMemory |
In-memory key store for tests. |
Vellum.Rotation |
Opt-in background DEK rotation (Phase 4). |
Vellum.AspNetCore |
DI extensions + health checks (Phase 4). |
Quickstart
Install the packages:
dotnet add package Vellum.Abstractions
dotnet add package Vellum.Core
dotnet add package Vellum.Vault
dotnet add package Vellum.EntityFrameworkCore
Wire Vellum into an ASP.NET Core / generic host app. Options for the EF Core store
flow via UseVellum on the DbContextOptionsBuilder — configure them once, no duplication:
// Program.cs
services.AddVellum(o => o.DekCacheTtl = TimeSpan.FromMinutes(30));
services.AddVaultProvider(o =>
{
o.Address = "http://vault:8200";
o.Token = builder.Configuration["Vault:Token"]!;
o.KeyName = "my-kek";
});
services.AddDbContext<AppDbContext>(options => options
.UseNpgsql(builder.Configuration.GetConnectionString("App"))
.UseVellum(v => v.UniqueActiveIndexFilter = "\"IsActive\" = true"));
services.AddEntityFrameworkCoreStore<AppDbContext>();
// AppDbContext.cs
public sealed class AppDbContext(DbContextOptions<AppDbContext> opts) : DbContext(opts)
{
protected override void OnModelCreating(ModelBuilder mb)
{
base.OnModelCreating(mb);
mb.AddVellumEncryptionKeys(this);
}
}
// Usage (inject IPayloadEncryptor anywhere)
EncryptedPayload envelope = await encryptor.EncryptStringAsync("hello", scope: "tenant:42");
string roundtrip = await encryptor.DecryptStringAsync(envelope);
The envelope carries its own wrapped DEK, so decryption is self-contained — no second
DB round-trip. DecryptAsync is backed by an in-memory cache keyed by the wrapped
ciphertext (see #6), so read-heavy
workloads pay at most one KEK round-trip per distinct DEK.
For more complete wiring — feature-flagged rollouts, snake_case schemas, migrations
from a legacy encryption layer, appsettings.json bridging — see the
samples/ folder and docs/consumer-options-bridging.md.
Design-time scaffolder (dotnet ef migrations add)
dotnet ef prefers an IDesignTimeDbContextFactory<T> over the host-build path when
both are available. A factory that forgets to call UseVellum(...) produces
DbContextOptions without the Vellum options extension, so the scaffolder generates a
migration against Vellum's defaults (PascalCase column names, SQL Server filter
syntax) even if the runtime AddDbContext pipeline is configured correctly.
This is issue #17.
The recommended fix is to declare the Vellum schema once on
the DbContext class with [VellumEntityFrameworkOptions]. The attribute is
reflection-readable at both runtime and design time, so the scaffolder picks up the
overrides with no duplication between Program.cs and the factory:
[VellumEntityFrameworkOptions(
TableName = "bus_encryption_keys",
KeyIdColumnName = "key_id",
ScopeColumnName = "scope",
WrappedCiphertextColumnName = "wrapped_ciphertext",
WrappedProviderVersionColumnName = "wrapped_provider_version",
CreatedAtColumnName = "created_at",
ExpiresAtColumnName = "expires_at",
IsActiveColumnName = "is_active",
UniqueActiveIndexFilter = "\"is_active\" = true")]
public sealed class AppDbContext(DbContextOptions<AppDbContext> opts) : DbContext(opts)
{
protected override void OnModelCreating(ModelBuilder mb)
{
base.OnModelCreating(mb);
mb.AddVellumEncryptionKeys(this);
}
}
The attribute resolution order is: (1) UseVellum(...) on the options builder, (2)
[VellumEntityFrameworkOptions] on the context type, (3)
IOptions<VellumEntityFrameworkOptions> from the application service provider, (4)
defaults. Every attribute property the consumer leaves unset keeps the Vellum
default, so a minimal decoration like
[VellumEntityFrameworkOptions(TableName = "bus_encryption_keys")] is valid.
As an alternative, an IDesignTimeDbContextFactory<T> can call UseVellum(...)
explicitly alongside the provider extension:
public sealed class AppDbContextFactory : IDesignTimeDbContextFactory<AppDbContext>
{
public AppDbContext CreateDbContext(string[] args)
{
DbContextOptionsBuilder<AppDbContext> b = new();
b.UseNpgsql("Host=localhost;Database=app;Username=postgres;Password=postgres");
b.UseVellum(v => v.UniqueActiveIndexFilter = "\"IsActive\" = true");
return new AppDbContext(b.Options);
}
}
Either pattern works. The attribute is cleaner when the Vellum schema is fixed at compile time; the factory pattern is more flexible when options come from a configuration source the factory can read directly.
Building locally
Requires the .NET 10 SDK (see global.json) plus the .NET 8 and .NET 9 runtimes for running the multi-target test suite.
dotnet restore --locked-mode
dotnet build -c Release
dotnet test -c Release
--locked-mode is mandatory: Vellum commits packages.lock.json for every project so transitive versions are pinned and auditable. Any drift fails the restore.
Design principles
- Provider-agnostic — Vault, AWS KMS, Azure Key Vault, GCP KMS, static (dev) are first-class.
- Storage-agnostic — EF Core by default, but any storage can implement
IEncryptionKeyStore. - No custom crypto — exclusively AES-GCM via
System.Security.Cryptography. - Multi-tenant by construction — keys scoped via an opaque
Scopestring. - Fail closed — any error in key resolution, wrap, or unwrap throws. Never silently degrades.
- Zero allocation where possible —
Span<byte>/Memory<byte>on the hot path. - Testable — every interface mockable; in-memory provider + store for unit tests.
- No background services in Core — rotation is opt-in via
Vellum.Rotation.
Security
Please see SECURITY.md for responsible disclosure and threat model.
License
Apache License 2.0. See LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.9)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.9)
- Microsoft.Extensions.Options (>= 9.0.9)
- Vellum.Abstractions (>= 0.1.0)
-
net8.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.9)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.9)
- Microsoft.Extensions.Options (>= 9.0.9)
- Vellum.Abstractions (>= 0.1.0)
-
net9.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.9)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.9)
- Microsoft.Extensions.Options (>= 9.0.9)
- Vellum.Abstractions (>= 0.1.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0 | 113 | 4/13/2026 |