Skip to content

docs(security): write the canonical ZeroKMS cryptography page#62

Merged
coderdan merged 6 commits into
v2from
docs/v2-security-architecture
Jul 9, 2026
Merged

docs(security): write the canonical ZeroKMS cryptography page#62
coderdan merged 6 commits into
v2from
docs/v2-security-architecture

Conversation

@coderdan

@coderdan coderdan commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

/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:

Old claim Where Resolution
"HMAC-based key derivation scheme" security-architecture.mdx True of layer 2: the SDK expands the key seed into a per-value data key, client-side
"Proxy symmetric re-encryption (patent pending)" kms/index.mdx True of layer 1: ZeroKMS combines the authority key with the client key component to produce the seed, server-side
"Root keys stored in AWS KMS. ZeroKMS handles key derivation and wrapping" kms/index.mdx True of the authority key at rest. AWS KMS protects it; no per-value data key is ever wrapped or stored
"No caching needed" / "never cached" security-architecture.mdx Too broad, and now corrected

So 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.mdx said data keys are "never stored, cached, or transmitted" and that there is "no caching needed". Meanwhile Proxy ships cipher_cache_size=64, cipher_cache_ttl_seconds=3600, and a cipherstash_proxy_keyset_cipher_cache_hits_total metric. 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

  • Standardises on AES-256-GCM-SIV. The corpus was split 6/3 between AES-GCM-SIV and AES-256-GCM (plain GCM would have overclaimed nothing and underclaimed nonce-misuse resistance).
  • Rewrites security/index.mdx from a stub into a real section door, since the CISO audience is meant to read this section end to end.
  • Does not restate the index-term leakage model. That belongs to /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 build passes, 716 pages. /security/architecture prerenders.
  • validate-content passes.
  • All internal links resolve.
  • No em-dashes.

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, and reference/security-architecture.mdx still carry the unreconciled claims, and three pages still say AES-256-GCM. Two of those are compliance pages. Tracked separately.

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.
@vercel

vercel Bot commented Jul 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
public-docs Ready Ready Preview, Comment Jul 9, 2026 8:15am

Request Review

…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.
@coderdan coderdan changed the title docs(security): write the canonical ZeroKMS architecture page docs(security): write the canonical ZeroKMS cryptography page Jul 9, 2026
@coderdan

coderdan commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Pushed 005d22c with two follow-ups from review.

Renamed /security/architecture -> /security/cryptography (H1, nav item, and slug). Also repointed the /stack/reference/security-architecture redirect at the new destination, and ticked the item in IA.md. validate-redirects still passes at 194 pages.

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 title, so renaming the frontmatter would have changed the H1 too. I added a navTitle field to the v2 frontmatter schema, applied in getV2PageTree alongside the existing flattenEmptyFolders transform. It overrides the sidebar label only; URL and H1 are untouched.

Verified against the built HTML:

/docs/security               -> 'Overview'
/docs/security/cryptography  -> 'Cryptography'
/docs/security/compliance    -> 'Compliance'

H1 on /security:              Architecture & security
H1 on /security/cryptography: Cryptography

navTitle is reusable: every other section index has the same duplication (/reference sits under a "Reference" folder and is itself labelled "Reference", and so on). Worth applying across the board when the index pages get filled in.

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.
@coderdan coderdan merged commit 353615c into v2 Jul 9, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant