ZSC — the Zeq Secure Context
ZSC is a core feature: the framework's entire secret surface. There are no
.env files anywhere in Zeq. Every secret — session keys, Stripe keys, the
node's Ed25519 identity, database URLs for sub-systems — lives encrypted at
rest in the zsc_secrets table and is decrypted into process.env only in
memory, only at boot.
The cipher
Each secret row stores value_enc + value_iv, sealed with:
- AES-256-GCM, 12-byte IV, 16-byte auth tag appended to the ciphertext, hex-encoded.
- Key = PBKDF2-SHA256(node field-key bytes, HULYAS salt, 200,000 iterations, 32-byte output).
- HULYAS salt =
HULYAS.ZeqField.f=1.287Hz.τ=0.777s.α=1.29e-3— the kernel constants, bound into the key derivation itself.
The algorithm is defined once in shared/api-core/src/lib/zeqField.ts and
mirrored byte-for-byte by the launcher and the bootstrap tool, so a value
sealed by any of them opens with any of the others.
The bootstrap problem, and the vault-adjacent answer
The vault is opened by exactly two bootstrap pointers that, by definition, cannot live inside the thing they unlock:
ZEQ_FIELD_KEY— 64 hex; the root the row key is derived from.DATABASE_URL— where the vault rows are read from.
At boot, zeq-dev-launch.mjs takes those two, connects to Postgres, decrypts
every zsc_secrets row, populates process.env, and spawns the server. A
shell-set value always wins over a vault row — the operator's incident-response
override path.
Because those two pointers can't be in the vault, they live vault-adjacent:
a sealed store (infra/zsc-bootstrap.mjs → infra/per-domain/.zsc-bootstrap),
ciphertext-only on the host, encrypted with the same zeqField cipher under
one master pointer, ZEQ_BOOTSTRAP_KEY. The master pointer is itself a
zsc_secrets row — so the vault protects the keys to its own front door — and
is held by the operator. One pointer opens the store; the store yields each
domain's two pointers in memory; those open each domain's vault. No secret is
ever written to disk in plaintext, and nothing sensitive is printed.
Every secret read is audited
Eliminating the .env file isn't only about encryption at rest — it's about accountability. A
plaintext .env is read silently and invisibly by anything in the process; no one can say later which
code read which secret, or why. ZSC closes that. Code doesn't reach for an ambient process.env.X; it
asks the context:
ZeqContext.read(name, { purpose, zid })
Every read emits one row to the audit chain — transition_type = 'secret_read' — carrying a
proof_digest bound to the requester's ZeqID, the Zeqond, and the stated purpose. So a secret
access is no longer a silent side effect; it's an attributable, provable event you can inspect in the
explorer after the fact: this identity read this secret at this tick for this
reason. A .env file can't tell you any of that, which is exactly why the framework doesn't use one
and recommends you don't either.
As a service, ZSC is per-machine (multi-tenant) by construction: a machine's secrets are sealed under an HKDF subkey derived from that machine, so cross-machine access isn't merely forbidden by a check — it's cryptographically impossible, because machine A's subkey will not validate machine B's AES-GCM tag.
Recreating a node
export ZEQ_BOOTSTRAP_KEY=<the master pointer, from your ZSC vault>
node infra/zsc-bootstrap.mjs up <domain> --force-recreate # decrypt in-memory → compose up
node infra/zsc-bootstrap.mjs seal # after rotating a key: reseal from the running fleet
Why it matters
Secrets bound to the kernel constants, decrypted only in memory, with the bootstrap keys themselves sealed under a single vault-held master — that is what lets a Zeq node boot from cold with no plaintext secret ever touching the filesystem. ZSC is the security spine the rest of the framework stands on.