Documentation
Deployment & CLI
Deploy the flare-encrypt store to your own Cloudflare account: fe init / wrangler deploy, D1, the KEK, and the freshness Durable Object. KEK custody, the rotation runbook, bootstrap gating, the Logpush→R2 audit sink, fe run injection safety, break-glass offline decrypt, and the fe key signing verbs with the custody drill.
What deploys
The store is a single standalone Cloudflare Worker plus one D1 database, one master-key secret (Secrets Store, or a plain Worker Secret as a config-only fallback), and a sharded freshness Durable Object — all on your own account. There is no hosted control plane in the loop: you own the code, the database, and the master key. The Worker binds Secrets Store directly, which gives per-accessenv.KEK.get() rather than an ambient secret.
Deploy to your own account
fe init --dry-run prints the ordered deploy plan — every step is a wrangler or D1 primitive, so nothing is hidden: create the D1 database, apply migrations, generate and provision the master key, deploy, mint the first admin token via /admin/bootstrap, delete the one-shotBOOTSTRAP_TOKEN, and store the endpoint and admin token in the OS keychain. You run the steps yourself — automatic execution isn't wired yet, so nothing runs against your account implicitly.
fe init --dry-run # print the plan: create D1 + migrate + provision KEK + deploy + bootstrapThe steps are the same primitives, run by hand:
wrangler d1 create flare-encrypt # note the database_id
wrangler d1 migrations apply flare-encrypt --remote # 0001_init.sql
# KEK as a two-level per-environment JSON map, keyed by environment id:
printf '{"%s":{"1":"%s"}}' "$ENV_ID" "$(openssl rand -base64 32)" \
| wrangler secrets-store secret put flare-encrypt-kek --store-id <store-id>
wrangler secret put BOOTSTRAP_TOKEN # one-shot; deleted after bootstrap
wrangler deployThe Worker binds three things: DB (the D1 database — everything in it is non-secret metadata or AES-GCM(...) = ct || tag), KEK (fetched per request viaenv.KEK.get()), and FRESHNESS (the Durable Object on both the write and read paths). Non-secret build config lives in vars — AAD_FMT,DEFAULT_ENV, and TIME_TRAVEL_WINDOW_MS — and never key material.
KEK custody
The master-key value is a two-level, per-environment JSON map — never a bare key. Outer keys are environment ids; inner keys are stringified kek_kv integers; inner values are base64 of the raw 32-byte AES-256 key:
{ "<environment_id>": { "1": "<base64-32B>", "2": "<base64-32B>" } }- Per-environment material. Each environment holds its own key bytes per version, so
kek_kvindexes into that environment's sub-map and a KEK is resolved by(environment_id, kek_kv). Two environments both atkek_kv = 1hold different bytes. A KEK compromise unwraps only that environment's DEKs, and a version can be purged or rotated for one environment without touching any other. - Generate with a CSPRNG. Each key is 32 raw bytes from
openssl randorcrypto.getRandomValues, never a passphrase. The Worker imports each needed version to anextractable:falsehandle and never logs the raw bytes. - Secrets Store or a Worker Secret. Operators without the Secrets Store beta use the identical two-level value as a plain Worker Secret; every code path is the same. The only capability lost is rotate-without-redeploy, and a Worker Secret is ambient in
env— access it behind one narrow module and never log or serializeenv.
Retention is coupled to erasure: a retained old KEK version is a live wrapping key. Keep a version in an environment's sub-map only while a live DEK there still references it. Once rotation reports zero DEKs on (environment_id, kek_kv = N), purge "N" — pruning it early bricks those tenants with KekVersionUnknown.
Rotation runbook
This is crypto-key rotation, distinct from connector-driven secret-value rotation. Every rotation returns 202 { jobId } and runs in a resumable Workflow — never a synchronous handler, which would exhaust the D1 subrequest and Worker CPU budget mid-pass and leave DEKs half-rewrapped.
- KEK rotation (proactive) — O(tenants), zero data re-encryption. Add a new version to that environment's sub-map, then
fe rotate kek --project app --env production. The Workflow re-wraps each of that environment'stenant_deksrows (unwrap under the old KEK, re-wrap under the new — same DEK plaintext), at most 200 DEKs per step. Pollfe rotate statusuntil zero DEKs remain on the oldkek_kv, then purge it. - DEK rotation — O(that tenant).
fe rotate dek … --tenant <ulid>mints a newdek_verand re-encrypts that tenant's secrets under it, at most 50 secrets per step, guarded on the value revision so a concurrent live write is never clobbered. The superseded DEK row goesretiringand is dropped once no secret references it. - Compromise-driven rotation. A KEK leak needs the heavier operation: generate fresh DEKs for every tenant in the compromised environment, re-encrypt all of that environment's data, and purge the compromised
kek_kvfrom that environment's sub-map. Because key material is per-environment, recovery is scoped to that one environment.
A KEK-map entry (or an old dek_ver) is never pruned until the completion count reaches zero; tenant_deks.status (active → retiring → revoked) is driven by the Workflow, not set by hand. An aad_fmt bump uses the same machinery: new writes set the new format, a Workflow re-encrypts old rows, and a golden vector for the new format exists before it is used.
Bootstrap gating
A Worker cannot delete or rewrite its own binding at runtime, so the store never claims a secret "self-deletes." Bootstrap is gated on a D1-observable, irreversible condition instead:POST /admin/bootstrap succeeds only while zero admin tokens exist inapi_tokens; once the first admin is minted, the endpoint refuses forever. The first call additionally requires the deploy-time BOOTSTRAP_TOKEN Worker Secret as a second factor. The init plan calls the endpoint, deletes BOOTSTRAP_TOKEN via wrangler as defense-in-depth, and ends with a step to force-rotate the admin token and mint least-privilege per-tenant tokens for real use.
fe login --url https://flare-encrypt.<subdomain>.workers.dev --token fe_...
fe token create --project app --env production --tenant <ulid> --cap secrets:read,secrets:write
fe token revoke <token-id>A tenant = NULL (cross-tenant) token requires an explicit admin orsecrets:export-all capability — token scoping is the only read-time tenant-isolation control. token.create and token.revoke are audited, and a token value is returned once at mint.
The audit sink
The in-D1 hash chain is tamper-evident, but the principal it holds accountable can rewrite it wholesale, so every audit row also streams to an immutable external sink via Logpush → R2 with Object Lock in compliance mode. That mirror is the only record that survives full-account compromise, and it is reconciled against the in-D1 chain to catch a full-account attacker.
wrangler r2 bucket create flare-encrypt-audit
wrangler r2 bucket lock set flare-encrypt-audit --retention-days 3650 # Object Lock, compliance mode
# Logpush job: audit dataset → the locked R2 bucket; verify the objects are immutable.fe run — the injector
fe run -- <cmd> is the runtime path for non-Workers consumers. It does one export round-trip (a single audited event), holds the values in process memory, and hands them to the child through execve's environment — no temp file, no generated .env, no shell-history exposure. Two OS-level protections back this up, because "never on disk" is a claim about the tool's own file I/O, not about memory or the child's environment:
- argv rejection.
fe setrefuses a value passed as a positional argument (it would appear inps aux,/proc/PID/cmdline, and shell history) and requires--stdinor a no-echo prompt. - No core dump.
fe runsetsulimit -c 0on the child shell beforeexec, so a crash of the injected process cannot dump the secret set to/coresor/var/crash. Credentials live in the OS keychain, never a dotfile.
The honest guarantee: the CLI writes no plaintext file, but OS swap and core-dumps remain the host's responsibility — disable them for high-assurance use. Env-var injection is inherently visible in the child's /proc/PID/environ, ps e, and crash dumps; that is documented, not marketed past.
Break-glass — offline decrypt
Because @fractalboxdev/flare-encrypt-sdk is a pure leaf with zero Cloudflare dependencies that derives the encryption context deterministically from row fields, you decrypt an exported D1 snapshot with just the target environment's KEK bytes and Node — no Worker, no D1 runtime, no dashboard:
wrangler d1 export flare-encrypt --output ./snapshot.sql --remote
fe export --offline --snapshot ./snapshot.sql --kek ./kek.json --tenant <ulid>
# kek.json is the flat single-environment version map for the target environment —
# that environment's sub-map from KEK custody: { "1": "<base64-32B>", "2": "<base64-32B>" }
# (--tenant is optional; omit it to decrypt every tenant in the snapshot)
#
# Under the hood, using only public SDK primitives:
# importKek(kekBytes, kek_kv) → unwrapDek(KEK, wrappedDek, tenantId) → decrypt(DEK, record, dataCtx)This is the one CLI path that touches disk deliberately, and it is what makes "you fully own it" provable when Cloudflare is unavailable — the concrete differentiator from a hosted vault, which has no portable offline read.
The offline kek.json is a plaintext copy of the root wrapping key. Holding it next to a D1 export deliberately reconstructs the full-KEK-plus-full-D1 condition the rest of the design avoids, so it is break-glass only: escrow- or HSM-sourced, materialized on an isolated host, audited and dual-control, and destroyed after use.
Signing keys — fe key, and the custody drill
The signing-key surface has the same operator verbs. fe key create prints only the public key; fe key sign reads the message from stdin — never argv — and signs the exact bytes; fe key rotate activates a new version while prior versions stay verifiable as retiring. A sign-only CI credential is one command:
fe key create release # → base64 public key, for verifier distribution
fe key sign release --stdin < artifact.bin # → base64 signature (message from stdin ONLY)
fe key rotate release # v+1 signs; prior versions stay verifiable
fe token create --name ci-release --cap keys:sign --key release \
--project app --env production --tenant <ulid> # signs that one key; can read nothingWrapped signing keys live in the same D1 export, so key custody survives a store outage under the same "you fully own it" guarantee. The custody drill proves it offline:
wrangler d1 export flare-encrypt --output ./snapshot.sql --remote
fe key drill --snapshot ./snapshot.sql --kek ./kek.json # custody proof, no private material
fe key drill --snapshot ./snapshot.sql --kek ./kek.json --reveal # break-glass proper: PKCS#8 keysThe default drill unwraps every wrapped version, signs a challenge, and verifies it against the stored public key — custody is proven without a private key ever being printed.--reveal trades the never-leaves property for survivability and is treated as key exposure: run it on an isolated host under the KEK-custody rules above, thenfe key rotate each revealed key and retire the revealed versions.
Operations quick reference
| Task | Command |
|---|---|
| First deploy | fe init --dry-run → run the printed steps |
| Add a KEK version | put {"<env_id>":{"1":…,"2":…}} to KEK |
| KEK rotate | fe rotate kek … → fe rotate status |
| DEK rotate | fe rotate dek … --tenant <id> |
| Crypto-shred a tenant | DELETE /v1/tenants/… (reports erasure_eta) |
| Break-glass read | wrangler d1 export + fe export --offline |
| Create / rotate a signing key | fe key create <name> / fe key rotate <name> |
| Sign from CI | fe key sign <name> --stdin under a keys:sign key-pinned token |
| Signing custody drill | wrangler d1 export + fe key drill … (--reveal ⇒ rotate + retire) |
| Mint a scoped token | fe token create … (--cap keys:sign --key <name> for sign-only) |
The schema behind these commands is the Data model; the trust boundaries they assume are the Security model.