docs(security): write the canonical ZeroKMS cryptography page#62
Conversation
The docs described ZeroKMS three ways, and each description was a different layer of the same mechanism with the others omitted. Reconciled per product review: - Proxy symmetric re-encryption (patent pending) produces the key seed server-side. HMAC-based key derivation expands that seed into a unique data key per value, client-side. Both are true; neither alone is the story. - An AWS KMS root key protects the authority key at rest. The authority key is the server half of the dual-party split. No per-value data key is ever wrapped or stored, so both the "AWS KMS backed" and "no wrapped-key storage" claims hold once the layers are separated. - "No caching needed" was too broad. Proxy caches keyset-scoped ciphers (cipher_cache_size, cipher_cache_ttl_seconds). No data key is ever cached, stored, or transmitted. The page states both, in a table. Standardises on AES-256-GCM-SIV. Leaves the index-term leakage model to /concepts/searchable-encryption, per the anti-drift rule, and links to it.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…Overview - /security/architecture -> /security/cryptography (H1, nav, slug). Updated the v2-redirects.mjs destination for /stack/reference/security-architecture and ticked the item in IA.md. - The section index nav item read "Architecture & security", duplicating the folder it sits under. Fumadocs takes the sidebar label and the H1 from the same `title`, so add a `navTitle` frontmatter field that overrides the sidebar label only, applied in getV2PageTree. The URL and H1 are untouched. Reusable for the other section indexes, which have the same duplication.
|
Pushed Renamed Section index nav item now reads "Overview". It was "Architecture & security", duplicating the folder label directly above it. That one needed a small mechanism. Fumadocs derives both the sidebar label and the H1 from a page's Verified against the built HTML:
|
The key-derivation diagram was ASCII in an unlabelled code fence: monospace, ~80 columns, overflowing the content column. Adds Mermaid, authored as a ```mermaid fence rather than a component call. The fence survives the markdown pipeline untouched; the Shiki transformer copies its source onto the <pre> as data-mermaid, and TrackedCodeBlock swaps in the diagram at render time. That ordering is deliberate. fumadocs-mdx serializes the same mdast tree it renders, so a remark plugin rewriting the fence into <Mermaid chart="..." /> also rewrites the markdown we serve at .mdx and in llms.txt, where a diagram should degrade to readable source, not an opaque component call with escaped entities. Shiki ships a mermaid grammar, so the fence is a valid code block everywhere it isn't a picture. Mermaid is ~1MB and is imported dynamically inside the effect, so only pages with a diagram load it and it never enters the server bundle. The component re-renders on theme toggle, since Mermaid bakes colours into the SVG.
Also gates the build on diagram validity. Mermaid parses in the browser, and the component catches errors so a bad chart can't blank the page. That makes the failure mode silent: a diagram that renders as nothing looks exactly like a diagram nobody noticed was missing. `validate-mermaid` parses every ```mermaid fence at build time (mermaid needs a DOM, so jsdom provides one) and fails with the file and line. Verified by planting a broken diagram. The component now falls back to rendering the diagram source rather than nothing, for the render step the parse gate can't cover. The two deployment diagrams gain what ASCII couldn't express: the key-seed exchange is bidirectional, and a crossed edge shows that EU ciphertext cannot be decrypted by the APAC app. Region identifiers now carry the .aws suffix, matching the regions table.
The diagram and prose had the client key flowing into ZeroKMS as a "derivation input", which inverts the property the architecture exists to provide. ZeroKMS re-encrypts under the authority key alone and returns a key seed. It never receives the client key, and the request carries no key material. The client key processes the seed in your infrastructure, and an HMAC-based KDF expands the result into a per-value data key. Only the seed crosses the boundary, and it crosses inward. Nothing crosses outward: not the client key, not the processed seed, not the data key. Fixes the layer 1/2 prose, the diagram, the trust-model bullets, and both data-flow paths. Also tightens data-residency, where "both must cooperate" implied the halves meet somewhere, and where client keys were said to never leave the region rather than never leave your infrastructure.
The diagram had the key seed arriving from nowhere. The application requests it by sending a key ID, which is what gives each data key a unique identity and lets the same seed be requested again to decrypt the value. Outbound across the boundary: a key ID, which is not key material. Inbound: the key seed. Nothing else.
/security/architecture, the page that picks one ZeroKMS story. Everything else in the security section is blocked on this.The three accounts turned out to be one
I expected to have to discard two descriptions. Instead, each was a different layer of the same mechanism, stated as if it were the whole thing. Adjudicated with @coderdan:
security-architecture.mdxkms/index.mdxkms/index.mdxsecurity-architecture.mdxSo the page separates the layers explicitly, with a diagram, and says why naming only one of them is incomplete.
The caching claim was wrong and is now stated plainly
security-architecture.mdxsaid data keys are "never stored, cached, or transmitted" and that there is "no caching needed". Meanwhile Proxy shipscipher_cache_size=64,cipher_cache_ttl_seconds=3600, and acipherstash_proxy_keyset_cipher_cache_hits_totalmetric. A reviewer who reads both pages catches us.What's actually true: Proxy caches keyset-scoped cipher objects; no data key is ever cached. The page carries a table making the distinction, and an info callout saying the earlier claim was too broad. Caching a keyset cipher isn't caching a data key: the cipher still needs the client key to derive anything, and per-value keys are still derived per value.
This is the kind of thing a CISO finds, so we should find it first.
Also
AES-GCM-SIVandAES-256-GCM(plain GCM would have overclaimed nothing and underclaimed nonce-misuse resistance).security/index.mdxfrom a stub into a real section door, since the CISO audience is meant to read this section end to end./concepts/searchable-encryption(still a stub, task pending). This page gives a one-paragraph summary and links, per the anti-drift rule. It does say plainly that HMAC leaks frequency and ORE leaks order.Checks
bun run buildpasses, 716 pages./security/architectureprerenders.validate-contentpasses.Follow-ups this unblocks and creates
Unblocks
/security/{zerokms,cts,stack-sdk,proxy}and/security/threat-scenarios, which must link here rather than restate.Creates cleanup on the legacy tree, which is still live at
/stack:kms/index.mdx,kms/keysets.mdx, andreference/security-architecture.mdxstill carry the unreconciled claims, and three pages still sayAES-256-GCM. Two of those are compliance pages. Tracked separately.