Skip to content
Security
Skill

/stash-zerokms

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

BOOST
From plugin
stack
16116 skills1 command
Install
$ npx -y skills add cipherstash/stack --skill stash-zerokms --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/stash-zerokms

Context 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

SKILL.md

stash-zerokms.SKILL.md
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: keysets, clients, and key management

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**.

Authentication and regions (see `stash-auth`)

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.

The model in one paragraph

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:

  • **Encrypt and query always use the client's bound keyset.** Search terms

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.

  • **Decrypt follows each payload's own keyset**, whichever client wrote it,

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.

The key hierarchy

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:

  • **ZeroKMS never possesses a usable data key** (zero-knowledge).
  • **Revoking one client blocks all of its future key operations, insta
Read more
Ships withstack

Searchable, application-level encryption for building privacy-first apps

Get the whole plugin

Other skills on stack.

stash-auth
Skill

stash-auth

How a CipherStash client authenticates — the canonical skill for credentials, auth strategies, and lock context. Covers the…

stash-cli
Skill

stash-cli

Drive CipherStash setup and encryption migrations through the `stash` CLI — `init`, `plan`, `impl`, `status`, `auth login`, `eql…