Per-environment wrap key
In Secrets Store or a Worker Secret — never in D1. It only wraps DEKs, so KEK rotation re-wraps keys without re-encrypting a row. Per-environment material contains a compromise.
versioned by kek_kv
AES-256-GCM envelope · Cloudflare Worker + D1
Your master key, your D1, readable without Cloudflare's dashboard. A Worker, a D1 database, and a KEK you hold — deployed to your own account with wrangler deploy. No vendor vault, no plaintext file.
The envelope
The envelope pattern AWS KMS and Google Cloud KMS use — a data key encrypts the data, a wrap key wraps the data key. flare-encrypt makes it multi-tenant and three-tier, with a mandatory encryption context on every operation.
In Secrets Store or a Worker Secret — never in D1. It only wraps DEKs, so KEK rotation re-wraps keys without re-encrypting a row. Per-environment material contains a compromise.
versioned by kek_kv
One AES-256 key per tenant, stored only wrapped. Dropping its row crypto-shreds the tenant; per-tenant scope keeps the GCM nonce budget comfortable.
versioned by dek_ver
AES-256-GCM sealed under the tenant's DEK with a frozen AAD of { tenant, column, dek_ver }. Copy the blob into another tenant's row and the tag check fails — placement integrity from crypto, not policy.
AAD = placement integrity
Confidentiality from the keys; placement integrity from the encryption context.Watch a relocation fail →
Signing keys
Beside secrets, the store is a signing oracle: a key — Ed25519 or ECDSA P-256 (ES256) — is generated inside the store, wrapped under the same master key, and rehydrated non-extractable per sign call — the private key never crosses the API. Send a message, get a signature; verify anywhere with just the public key.
A keys:sign token — optionally pinned to one key — gets signatures but cannot read secrets, list anything, or mint keys. Give CI a release-signing credential that is worthless for exfiltration.
cap keys:sign · per-key pin
Each sign call lands in the hash-chained audit log committing to the key version andsha256(message) — evidence of exactly what was signed, with the payload never logged.
key.sign { keyVer, msgSha256 }
The SDK's verify takes just the raw public key — no KEK, no network, no account. Rotation retains prior versions so old signatures keep verifying through the grace window. Ed25519 (AWS KMS cannot sign it) and ES256 on the exact JOSE wire — rawr‖s, never DER.
Ed25519 (EdDSA) · ES256
The private key's whole life stays inside your Cloudflare boundary — honest scope: an isolate, not an HSM. Read the signing model →
What "fully own" means
All key material and ciphertext lives in your account — no hosted vault, nothing in the read path.
Data in D1 you export, plus a KEK you hold — decrypt an export offline with the sdk, no Worker.
A per-environment KEK wraps per-tenant DEKs, so a compromise stops at the environment. An encryption context binds each ciphertext to its row.
The fe CLI streams secrets into a process via execve — never a .env file. Any runtime.
Signals it works
Layered by design
Plain TypeScript, zero runtime dependencies. Start with the library; graduate to the store for sharing, audit, and rotation.
@fractalboxdev/flare-encrypt-sdklibraryEmbeddable envelope library. Web Crypto only, byte-identical on Workers + Node 20+. Zero runtime deps.
adopt whenPer-tenant encryption in your own app, no service to deploy.
@fractalboxdev/flare-encrypt-clienttransportThin HTTP/RPC transport for a deployed store. Depends only on the sdk.
adopt whenCall a store without hand-rolling the wire protocol.
@fractalboxdev/flare-encrypt-workerthe storeThe deployable BYOC store: Worker + D1 + KEK, scoped-token auth, audit log. You fork and deploy it.
adopt whenA shared, audited store on your account.
feCLIInjects secrets into any runtime via execve — never a .env file.
adopt whenSecrets for any process, off the filesystem.
Step through the envelope, watch a relocation fail, then point the live panel at your store.