Credential custody · service
Credentials held for people.
Read only by the workload that may.
Secrets keeps the tokens and keys a workload holds on a person's behalf. A workload hands it a value; Secrets encrypts it in the application, stores only ciphertext in PostgreSQL, and gives it back only to a workload granted that exact tenant and action. The person who owns it can see its metadata, revoke it or delete it, and nobody reads a value back through a user route.
secrets.custody · ESS domain
- Entities2
Hover or focus an entity to see what it relates to.
- entities in the domain
- 2
- relations
- 0
- shipped, runs today
- 11
- planned
- 2
01 / What it does
Custody, not provider logic.
Secrets holds bytes, versions, ownership and audit records. OAuth exchange, refresh and upstream revocation stay in the service that integrates the provider.
Encrypted before storage
ShippedEvery version gets its own data key, wrapped by a versioned key-encryption key that PostgreSQL never sees. The ciphertext is bound to its tenant, reference, version and disclosure.
Tenant-bound, least privilege
ShippedWorkloads are Kubernetes service accounts granted explicit actions in one tenant; people are verified by Identity and act only on what they own.
Atomic prepared batches
ShippedA workload stages puts and deletes in one tenant and commits them in one PostgreSQL transaction, or aborts them.
Named secrets over several backends
ShippedAn in-process library with named, scoped secrets over the OS keychain and this service, one backend per namespace and no fallback, and secretsctl, a local CLI that never prints a secret. Read-only 1Password is planned.
02 / One write
From request to ciphertext.
Every route follows the same order: the caller is verified before storage is touched, and the value is encrypted before it reaches the database.
- HTTPBounded request
A JSON body of at most 1 MiB and a bearer token. Values travel only in bodies.
- AuthorityVerify the caller
Kubernetes TokenReview for workloads, Identity for people: subject, tenant and actions.
- PolicyCheck tenant and action
The route's action and the request's tenant must match the principal, or it is refused.
- CryptoSeal the value
A fresh data key, AES-256-GCM, wrapped by the active key with the reference as associated data.
- PostgreSQLCommit with its audit row
Ciphertext, wrapped key, metadata and one audit event in one transaction.
03 / Specification
What Secrets is specified in.
The secrets.custody domain of the ESS specification, retrofitted from the shipped service. A conformance suite of 111 scenarios runs it against the real router and store on every check. Generated from spec/ by secrets-docs.
ESS domain · secrets.custody
Encrypted, tenant-scoped custody of secret values, their revocation and deletion, and atomic prepared mutation batches.
- Entities2
- One
- Many
- Initial state
Hover or focus an entity to see its fields, lifecycle and relations. Press Enter to pin it, Escape to clear.
Lifecycles with transitions
SecretDomain as text
| Entity | Identity and fields | Lifecycle | Relations |
|---|---|---|---|
PreparedTransaction | transaction: uuid (identity) tenant: string actor: string mutations: List<Mutation> created_at: string expires_at: string | states Prepared; initial Prepared | — |
Secret | reference: Reference (identity) id: uuid owner_subject: string disclosure: Disclosure version: integer labels: Map<string, string> value: bytes created_at: string updated_at: string | states Active, Revoked; initial Active revoke: Active or Revoked → Revoked replace: Active or Revoked → Active | — |
Source: generated by secrets-docs from spec/ (ess specify compile); do not edit
04 / Status
Version 0.6.0. Here is what exists.
Shipped means in the released service today. Planned means specified and on the plan only.
Shipped · runs today
- ShippedWorkload routes: put, get, exists, list, deleteBehind Kubernetes TokenReview and a local grant file; one action per route.
- ShippedUser routes: list, detail, revoke, deleteBehind Identity; metadata only, and only for secrets the caller owns.
- ShippedPrepared batchesPrepare, commit and abort; sealed like a value, applied in one transaction, expiring after 600 seconds.
- ShippedStable refusal codesEvery refusal is {"error", "code"} and echoes no part of the request.
- ShippedEnvelope encryption with associated dataAES-256-GCM per version, a versioned keyring mounted read-only, and key rotation with secrets rewrap.
- ShippedAudit events for every changeWritten in the same transaction; no API reads them, and some actors are caller-chosen.
- ShippedRust workload clientsecrets-client covers every workload route.
- ShippedESS specification and conformance suitesecrets.custody retrofitted from the code and secrets.storage; 111, 110 and 89 scenarios run against the service, the library and the secretsctl binary on every check, every one answered.
- ShippedRewrap of every stored versionsecrets rewrap re-encrypts every stored version under another key, current or older.
- ShippedNamed, scoped secrets over several backendsThe secrets.storage library: OS keychain and this service behind one interface, one backend per namespace, the local authorizer first; the value a read returns is asserted by the conformance suite on both backends.
- ShippedLocal CLI that never prints a secretsecretsctl put, read --out, describe, list, delete, rename, namespace, mount set, bind and unbind in the local scope; --tenant and --user other than default are denied; a value only from a hidden prompt, a pipe or a mode-0600 file, and back only into a new mode-0600 file.
Planned · roadmap only
As of 2026-10-05 · Secrets' status record, maintained in the repository and checked against main