meta-issue-creation
How an agent files a GitHub issue on cipherstash repos — required structure (Background / Problem / Proposal), dumbed-down…
The ZeroKMS key model — keysets, clients, client keys, and the grant/revoke lifecycle. Covers the four-level key hierarchy, why every encrypt/decrypt/query is scoped to a keyset, the exact failure surface when a client lacks keyset access (unreachable keysets fail loudly; the
$ npx -y skills add cipherstash/stack --skill stash-zerokms --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/stash-zerokmsContext preview
The summary Claude sees to decide when to auto-load this skill.
The ZeroKMS key model — keysets, clients, client keys, and the grant/revoke lifecycle. Covers the four-level key hierarchy, why every encrypt/decrypt/query is scoped to a keyset, the exact failure surface when a client lacks keyset access (unreachable keysets fail loudly; the
name: stash-zerokms description: The ZeroKMS key model — keysets, clients, client keys, and the grant/revoke lifecycle. Covers the four-level key hierarchy, why every encrypt/decrypt/query is scoped to a keyset, the exact failure surface when a client lacks keyset access (unreachable keysets fail loudly; the one silent case is a reader granted the writer's keyset but bound to a different one — decrypt works, encrypted search returns zero rows), the workspace default keyset, multi-tenant isolation via `config.keyset`, and the ZeroKMS API for creating, granting, and revoking keyset access. Use when a decrypt or query fails with a ZeroKMS error, when planning multi-tenant key isolation, when deciding which credentials a backfill job or edge function should use, when rotating or revoking a compromised credential, or whenever another skill's guidance touches "credentials", "keysets", or "who can decrypt what" — this skill is the canonical source for that model.
ZeroKMS is the key service behind every CipherStash Stack operation. This skill is the canonical description of its access model. Other skills (`stash-edge`, `stash-deployment`, `stash-cli`, `stash-postgres`, `stash-supabase`) touch credentials and keysets in passing; where their wording and this skill disagree, **this skill wins**.
ZeroKMS accepts exactly one credential: a **CipherStash service token** — a short-lived signed JWT minted by CTS, the CipherStash token service. Access keys and IdP JWTs are never sent to ZeroKMS directly; they are exchanged at CTS for a service token first. The auth strategies do this for you — `stash-auth` is the canonical skill for that whole surface (strategies, `CS_*` variables, token contents, failure codes).
ZeroKMS runs in a number of regions (the current list: `stash auth regions`, or the table in `stash-auth`). Your workspace's region is part of its CRN (`crn:<region>.<provider>:<workspace-id>`), and CTS resolves the matching ZeroKMS endpoint and stamps it into the service token — the client discovers where ZeroKMS is from the token itself. You never configure a ZeroKMS URL, which is also why the `CS_*_HOST` override variables are debug-only and must not appear in CI or examples.
Every value is encrypted **under a keyset**. A **client** (an application credential with its own client key) is **bound to one keyset** — the one named in its config (`config.keyset`) or, when omitted, its default keyset, the one it was created against — and can additionally be **granted access** to others. The routing is asymmetric, and everything below follows from it:
are produced with a per-*keyset* index key the client loads from ZeroKMS at initialization. A client whose bound keyset is unreachable (no grant, revoked, disabled) fails **loudly** right there — construction, encrypt, and query alike.
and succeeds for any keyset the decrypting client is granted. A payload under a keyset the client has no grant for fails **loudly** at the ZeroKMS round trip (404).
Two corollaries that follow directly, and that agents get wrong most often:
1. **"Same credentials everywhere" is stronger than what's required.** Two different clients — each with its own client key — interoperate completely, search included, as long as both are **bound to the same keyset**. A backfill job and the deployed app may use different access keys; what must match is the *keyset* the operations run under, not the credential strings. Be precise about how that keyset is chosen: when an operation names one (`config.keyset`), it's that keyset; when it doesn't, ZeroKMS uses **that client's default keyset** — the keyset the client was bound to when it was created. So two clients that both omit `config.keyset` only interoperate if their *default keysets* are the same keyset. 2. **One silent failure mode exists, and it is exactly the asymmetry above.** A reader that is *granted* the writer's keyset but *bound* to a different one decrypts the writer's rows fine — while its query terms derive under its own bound keyset and match nothing: **zero rows, no error**. So "decrypt works but search returns zero rows" has three suspects, in order: the reader's bound keyset vs the writer's (this skill), the extractor index (`stash-indexing`), the operand cast / predicate form (`stash-postgres`). What can *not* cause it is the credential string — every client bound to the same keyset derives the same index key. The same-keyset rule therefore binds **writers and query readers**; decrypt-only readers need just a grant.
Four levels, each narrowing scope; no single component holds enough material to derive a data key alone.
| Level | Key | Held by | Purpose | |---|---|---|---| | 1 | Root key | HSM / hardware root of trust | Protects all downstream key material. Never exported. | | 2 | Authority key (per keyset) | ZeroKMS | Derives key seeds for a keyset. Materialized per client grant, so every granted client resolves the same keyspace. | | 3 | Client key (per client / device) | Application runtime only | Never transmitted to ZeroKMS. Multiple clients can share a keyset, each with its own key. | | 4 | Data key (per value) | Derived in-process, ephemeral | Derived from client key + key seed during encrypt/decrypt. Never stored or transmitted. |
ZeroKMS uses proxy symmetric re-encryption: it sends key *seeds*, never usable keys, and the client combines a seed with its own client key to derive each per-value data key. Because the data key requires both halves:
Searchable, application-level encryption for building privacy-first apps
How an agent files a GitHub issue on cipherstash repos — required structure (Background / Problem / Proposal), dumbed-down…
How an agent authors branches, commits, and pull requests on cipherstash/stack — naming, signed commits, the…
How a CipherStash client authenticates — the canonical skill for credentials, auth strategies, and lock context. Covers the…
Drive CipherStash setup and encryption migrations through the `stash` CLI — `init`, `plan`, `impl`, `status`, `auth login`, `eql…
Deploy a CipherStash encryption rollout to a live environment without losing data — the multi-deploy ladder (schema-add +…
Integrate CipherStash encryption with Drizzle ORM using @cipherstash/stack-drizzle (EQL v3). Covers the types.* encrypted column…