@nebutra/vault
v0.1.1
Published
> **Status: Foundation** — Type definitions, factory pattern, and provider stubs are complete. Provider implementations require external service credentials to activate. See inline TODOs for integration points.
Readme
Status: Foundation — Type definitions, factory pattern, and provider stubs are complete. Provider implementations require external service credentials to activate. See inline TODOs for integration points.
@nebutra/vault
Application-layer secrets vault with envelope encryption for secure storage of customer credentials, API keys, OAuth tokens, and certificates.
Features
- Envelope Encryption — Unique Data Encryption Key (DEK) per secret, with Key Encryption Key (KEK) rotation
- Two Backends — AWS KMS (production) or local HKDF-based derivation (dev/self-hosted)
- Provider Agnostic — Switch between KMS and local without changing application code
- Tenant Isolation — Optional tenant ID support for multi-tenant systems
- Key Rotation — Rotate individual secrets or the KEK itself
Installation
pnpm add @nebutra/vaultQuick Start
import { getVault } from "@nebutra/vault";
// Auto-detects provider from environment
const vault = await getVault();
// Encrypt a secret
const encrypted = await vault.encrypt("my-api-key-12345", {
metadata: {
name: "stripe_api_key",
type: "api_key",
},
});
// Decrypt it back
const plaintext = await vault.decrypt(encrypted);
console.log(plaintext); // "my-api-key-12345"Architecture
Envelope Encryption
Each secret is encrypted using a two-layer encryption scheme:
┌─────────────────────────────────────┐
│ Root Key (AWS KMS or Master Key) │
└────────────┬────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Key Encryption Key (KEK) │ ← Encrypts DEKs
│ AWS KMS: Managed by KMS │
│ Local: Derived via HKDF from master │
└────────────┬────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Data Encryption Keys (DEK) │ ← One per secret
│ AWS KMS: Generated by GenerateKey │
│ Local: Random 32-byte key │
└────────────┬────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Secret Payload (AES-256-GCM) │ ← Your API key, token, etc.
└─────────────────────────────────────┘AWS KMS Provider
Uses AWS Key Management Service for KEK operations:
- Encrypt:
GenerateDataKeyreturns plaintext DEK + KMS-encrypted DEK - Decrypt:
Decryptrecovers plaintext DEK from KMS-encrypted version - Rotate: Generate new DEK, re-encrypt secret, discard old DEK
- KEK Rotation: AWS KMS CMK automatic rotation (configured in AWS console)
import { createVault } from "@nebutra/vault";
const vault = await createVault({
provider: "aws-kms",
keyId: "arn:aws:kms:us-east-1:123456789012:key/12345678-...",
region: "us-east-1",
});
const encrypted = await vault.encrypt("secret-token");
const plaintext = await vault.decrypt(encrypted);Local Provider
Derives KEK from a master key using HKDF (suitable for dev/self-hosted):
- Encrypt: Derive KEK → generate random DEK → encrypt DEK with KEK → encrypt secret with DEK
- Decrypt: Derive KEK → decrypt DEK with KEK → decrypt secret with DEK
- Rotate: Same as AWS KMS
- KEK Rotation: Increment version number; future secrets use new version, old ones remain decryptable
import { createVault } from "@nebutra/vault";
const vault = await createVault({
provider: "local",
masterKey: "my-ultra-secret-master-key-32-chars-min",
});
const encrypted = await vault.encrypt("secret-token");
const plaintext = await vault.decrypt(encrypted);API
createVault(config?: VaultConfig)
Create an explicit vault provider instance.
// AWS KMS
const vault = await createVault({
provider: "aws-kms",
keyId: process.env.AWS_KMS_KEY_ID,
region: "us-east-1",
});
// Local
const vault = await createVault({
provider: "local",
masterKey: process.env.VAULT_MASTER_KEY,
});getVault()
Get or create the default singleton vault provider. Auto-detects backend from environment.
const vault = await getVault();vault.encrypt(plaintext, options?)
Encrypt a plaintext secret.
interface EncryptOptions {
id?: string; // Auto-generated if omitted
tenantId?: string; // For multi-tenant systems
metadata?: {
name: string;
type: "api_key" | "oauth_token" | "credential" | "certificate" | "generic";
expiresAt?: string; // ISO-8601 datetime
};
}
const encrypted = await vault.encrypt("my-secret-api-key", {
id: "secret_abc123",
tenantId: "tenant_xyz",
metadata: {
name: "stripe_live_key",
type: "api_key",
expiresAt: "2026-12-31T23:59:59Z",
},
});
// Returns:
{
id: "secret_abc123",
ciphertext: "...", // base64
encryptedDek: "...", // base64
iv: "...", // base64
authTag: "...", // base64
keyVersion: 1,
algorithm: "aes-256-gcm",
tenantId: "tenant_xyz",
metadata: { ... },
createdAt: "2026-03-29T...",
}vault.decrypt(encrypted, options?)
Decrypt an encrypted secret.
interface DecryptOptions {
tenantId?: string; // Verify tenant ID matches
}
const plaintext = await vault.decrypt(encrypted, {
tenantId: "tenant_xyz", // Will throw if mismatch
});
console.log(plaintext); // "my-secret-api-key"vault.rotateKey(encrypted)
Rotate a secret in place — generate new DEK, re-encrypt data, return updated secret.
const rotated = await vault.rotateKey(encrypted);
// Same secret, new DEK, updated encryptedDekvault.rotateKek()
Rotate the Key Encryption Key itself. In AWS KMS, this is automatic; locally, increments version.
await vault.rotateKek();
// Future encryptions use new KEK version, old secrets remain decryptablevault.generateDek()
Generate a raw plaintext DEK (32 bytes). Useful for manual key management or testing.
const dek = await vault.generateDek(); // Buffer (32 bytes)vault.close()
Gracefully shut down the provider (closes KMS client, etc.).
await vault.close();Environment Variables
Auto-Detection
The factory auto-detects the provider from environment:
| Provider | Detection | Env Var |
|----------|-----------|---------|
| AWS KMS | Highest priority | AWS_KMS_KEY_ID or AWS_KMS_KEY_ARN |
| Local | Second priority | VAULT_MASTER_KEY |
| None | Throws error | No valid config |
# AWS KMS auto-detection
export AWS_KMS_KEY_ID="arn:aws:kms:us-east-1:123456789012:key/..."
export AWS_REGION="us-east-1"
# Local auto-detection
export VAULT_MASTER_KEY="my-ultra-secret-32-character-key"
# Explicit provider (optional)
export VAULT_PROVIDER="aws-kms" # or "local"AWS KMS Environment
AWS_KMS_KEY_ID=arn:aws:kms:us-east-1:123456789012:key/12345678-...
AWS_REGION=us-east-1
AWS_ACCESS_KEY_ID=... # Optional if using instance profile
AWS_SECRET_ACCESS_KEY=... # Optional if using instance profileLocal Environment
VAULT_MASTER_KEY=your-ultra-secret-master-key-here
# Optional: explicitly enable local provider
VAULT_PROVIDER=localSchemas (Zod)
Encrypt/decrypt operations use Zod schemas for validation:
import {
EncryptedSecretSchema,
SecretMetadataSchema,
} from "@nebutra/vault";
const encrypted = await vault.encrypt("secret");
const validated = EncryptedSecretSchema.parse(encrypted); // Type-safeExamples
Multi-Tenant Secrets
const vault = await getVault();
// Store a secret per tenant
const encrypted = await vault.encrypt("stripe-api-key-prod", {
tenantId: "org_acme",
metadata: {
name: "stripe_live",
type: "api_key",
},
});
// Decrypt only if tenant matches
const plaintext = await vault.decrypt(encrypted, {
tenantId: "org_acme", // ✓ Success
});
// This will throw:
await vault.decrypt(encrypted, {
tenantId: "org_evil", // ✗ Tenant mismatch
});Key Rotation
const vault = await getVault();
// Rotate a single secret
const updated = await vault.rotateKey(encrypted);
// encryptedDek changed, but plaintext is the same
// Rotate the KEK (all future secrets use new KEK version)
await vault.rotateKek();Custom Provider in Tests
import { setVault, LocalProvider } from "@nebutra/vault";
const testVault = new LocalProvider({
masterKey: "test-key",
keyVersion: 1,
});
setVault(testVault);
// Now getVault() returns testVault
const vault = await getVault();Security Considerations
- DEK Uniqueness: Each secret gets a unique DEK. Even identical plaintexts produce different ciphertexts.
- KEK Security: AWS KMS protects the KEK; for local provider, guard
VAULT_MASTER_KEYcarefully. - Rotation: Rotate KEKs periodically; Azure/AWS KMS support automatic rotation policies.
- Tenant Isolation: Optional tenant ID provides logical separation; ensure it's enforced at the application layer.
- Audit: Log all encrypt/decrypt operations for compliance (via
@nebutra/logger).
Limitations
- No Built-in Versioning: Store
rotatedAttimestamps to track secret age; rotation doesn't auto-expire old versions. - Plaintext in Memory: Decrypted secrets exist as plaintext in memory. Clear variables when no longer needed.
- No Expiration: Use
metadata.expiresAtas an application-level hint; vault doesn't auto-reject expired secrets.
Roadmap
- [ ] Support for Google Cloud KMS
- [ ] Secret versioning and history
- [ ] Automatic KEK rotation schedules
- [ ] Benchmarks and performance tuning
- [ ] Hardware security module (HSM) support
Testing
pnpm --filter @nebutra/vault typecheckLicense
Proprietary — Nebutra Inc.
