Create one
credential.add permission. label is the only required field.
Metadata is an arbitrary string map for things worth keeping next to the secret — a console URL, an
account number, a ticket reference. Tags are for filtering: GET /credentials?tag=production.
Read one
Listing and fetching never include the secret.GET /credentials returns a page of metadata;
GET /credentials/{id} returns one. To get the actual value:
Sharing by grant
Credentials are not copied between people. An admin grants a usercredential.read on the
credential — or on a group containing it — and the user reads it directly:
credential.read,
credential.update, credential.delete, and the global credential.add. Full model in
Permissions.
Gated credentials
Setrequires_permission on a credential and credential.read alone stops being enough. The holder
must also have an approved, unexpired permission request:
ttl_seconds runs from 60 seconds to 7 days. When it expires, access lapses on its own — nobody has
to remember to revoke it.
A request never creates authority. The requester must already reach the credential through a
direct or group
credential.read grant; the gate is a second factor on top, not a way in. Admins
cannot file requests, because they already have access.409, as does asking
for a credential that is not gated or that you can already reach. After a rejection or an expiry,
the member may ask again.
Every credential you can see reports your own standing in access_status:
Who has seen it
from, to, user_id,
action, and q; page with limit and cursor.
Admins get a second view answering a different question — not who did read it, but who could:
Deleting
DELETE /credentials/{id} needs credential.delete. The audit trail outlives the credential: the
delete is recorded with a null credential id and the name denormalized onto the row, so history
stays readable after the secret is gone.
How it is stored
Each credential’s password and notes are sealed as a single encrypted JSON blob under a per-secret AES-256-GCM data key, which is itself wrapped by the master key behind the vault’s key-wrapper interface —CUSTOS_MASTER_KEY in a simple deployment, a KMS or HSM in production.
If the key wrapper is not configured, every endpoint that encrypts or decrypts returns 503 rather
than silently storing plaintext.