Skip to content
Security
Skill

/stash-auth

How a CipherStash client authenticates — the canonical skill for credentials, auth strategies, and lock context. Covers the service-token model (every request to a CipherStash service carries a CTS-minted token; strategies exist to obtain one), the three separable concerns

BOOST
From plugin
stack
16116 skills1 command
Install
$ npx -y skills add cipherstash/stack --skill stash-auth --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-auth

Context preview

The summary Claude sees to decide when to auto-load this skill.

How a CipherStash client authenticates — the canonical skill for credentials, auth strategies, and lock context. Covers the service-token model (every request to a CipherStash service carries a CTS-minted token; strategies exist to obtain one), the three separable concerns

SKILL.md

stash-auth.SKILL.md
name: stash-auth
description: How a CipherStash client authenticates — the canonical skill for credentials, auth strategies, and lock context. Covers the service-token model (every request to a CipherStash service carries a CTS-minted token; strategies exist to obtain one), the three separable concerns people conflate (client credentials, end-user identity, key binding), the `@cipherstash/auth` strategies (`AutoStrategy`, `AccessKeyStrategy`, `OidcFederationStrategy`, `DeviceSessionStrategy`) and the Result-unwrap trap on `create()`, the four `CS_*` variables and `stash env`, credential discovery vs explicit config on the native and WASM entries, client lifetime with user-scoped strategies, `.withLockContext` and what is deprecated around it, and CTS failure codes. Use when an operation fails with 401/NOT_AUTHENTICATED/WORKSPACE_MISMATCH, when choosing between an access key and OIDC federation, when wiring per-user identity-bound encryption, when minting deployment credentials, or whenever another skill mentions "credentials" or `config.authStrategy` — this skill is the canonical source for that model.

Authenticating to CipherStash

Every request a Stack client makes to a CipherStash service authenticates with a **CipherStash service token**: a short-lived signed JWT minted by **CTS** (the CipherStash token service). That is the only credential ZeroKMS accepts. Access keys and identity-provider JWTs are never sent to ZeroKMS — they are *exchanged* at CTS for a service token, and everything in this skill is machinery for doing that exchange correctly and automatically.

This skill is canonical for authentication. Where other skills (`stash-edge`, `stash-encryption`, `stash-cli`, `stash-deployment`) touch credentials in passing and disagree with this one, this one wins. For what a token *authorizes* — keysets, clients, grants — see `stash-zerokms`: authentication and keyset access are separate gates, and a perfectly valid token still fails operations on a keyset its client was never granted.

The three concerns (do not merge them)

| Concern | What it decides | Where it's configured | |---|---|---| | **Client credentials** | Which workspace (`CS_WORKSPACE_CRN`) and — more granular than the workspace — which ZeroKMS *client* is acting (`CS_CLIENT_ID`, `CS_CLIENT_KEY`) and the access key that authenticates it (`CS_CLIENT_ACCESS_KEY`) | `CS_*` variables / dev profile / `config.accessKey` | | **End-user identity** | Authenticating the client *as* a specific user | `config.authStrategy: OidcFederationStrategy` | | **Key binding (lock context)** | Who can *retrieve a value's data key* — binds key access to a claim | `.withLockContext({ identityClaim })` per operation |

Note the granularity in the first row: only the CRN is about the *workspace*. The other three variables identify a specific **client** within it — its id, its key material, and its access key — so "the credentials" always means a particular client's identity, not a workspace-wide secret (see `stash-zerokms` for what a client is and what it can reach).

The classic conflation is between the last two: **an auth strategy decides who the client is; a lock context decides who can get a value's data key back.** They are orthogonal. Authenticating as a user via `OidcFederationStrategy` does not by itself bind any value to that user — without `.withLockContext()`, any client with the right keyset access can retrieve the value's key. Lock context *requires* `OidcFederationStrategy` on the Stack surface (there must be a user claim in the service token to bind to), but not the other way around.

The token exchange (CTS)

CTS's authorize endpoint exchanges exactly one of two credential kinds for a service token:

  • **A CipherStash access key** (`CSAK…`) — machine identity, for

services/CI.

  • **An OIDC JWT** from an identity provider configured for the workspace

(Clerk, Supabase, Auth0, or Okta — or CipherStash's own login) — end-user identity. CTS verifies it against the provider's JWKS and checks workspace membership.

The minted token carries: the subject (`CS|<user-id>` or `CS|CSAK<key-id>`), the workspace, scopes derived from the credential's role (member or admin — see `stash-zerokms` for what the scopes gate), and a built-in **service discovery mechanism** — the token itself tells the client where the workspace's services (ZeroKMS) live. The workspace CRN (`crn:<region>.<provider>:<workspace-id>`) names the region, CTS resolves the regional endpoints, and the client reads them from the token. You don't need to know how it works; the one fact that matters is that **a CipherStash-issued service token is only valid for services in the region it was issued for** — it cannot be replayed against another region. Endpoints are never hand-configured — the `CS_*_HOST` override variables are debug-only and must not appear in CI, examples, or docs.

CipherStash runs in a variety of regions across the world, and the set is **subject to change** — `stash auth regions` (add `--json` for machine-readable output) lists the current ones; see `stash-cli`. As of this writing:

| Region | Location | |---|---| | `us-east-1` | Virginia, USA | | `us-east-2` | Ohio, USA | | `us-west-1` | California, USA | | `us-west-2` | Oregon, USA | | `eu-west-1` | Dublin, Ireland | | `eu-central-1` | Frankfurt, Germany | | `ap-southeast-2` | Sydney, Australia |

A workspace lives in one region, chosen at creation (the `stash auth login` region picker / `--region`); the CRN records it thereafter.

CTS failures are deliberate and descriptive: `401` with a reason ("Principal X is not a member of workspace Y", "No OIDC provider found for issuer: …", "Access key was malformed: …"), and `402` ("Insufficient balance…") when the organisation is over its usage limit. A 402 is a billing problem, not a credentials problem — don't rotate keys over it.

The billing refusal, and why it needs its own handling

A token failure is normally transient (network, an

Read more
Ships withstack

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

Get the whole plugin

Other skills on stack.

stash-cli
Skill

stash-cli

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