Vault Security Model
This page explains what actually happens to a secret when you run
ribo vault set — how it is stored, when it
is decrypted, who can access it, and (just as important) what the vault does
not protect against. If you are deciding whether to put an API key, signing
secret, or database credential into a type = "vault" binding, this is the page
to read.
For usage, see ribo.toml Reference → vault
and the ribo vault command reference.
The lifecycle of a secret
- You set it.
ribo vault set my-cell API_KEYreads the value with hidden input (or from an environment variable) and sends it to the management API over HTTPS. It never touchesribo.toml, your git history, your shell history, or the deploy bundle. - It is encrypted immediately. The management API encrypts the value with AES-256-GCM — authenticated encryption with a fresh random nonce per write — before storing it. Only the resulting ciphertext is persisted, in Tissue's replicated configuration store.
- It stays encrypted at rest. The encryption key is a 256-bit platform key held only on the management host, separate from the replicated ciphertext — a copy of the storage layer alone is not enough to recover any secret.
- It is decrypted only at dispatch. When your Cell is loaded to serve a
request, the platform decrypts the value and injects it into the Cell's
environment (
env.API_KEY), over an internally-authenticated channel that is not reachable from the public internet. The plaintext exists only in memory, for the lifetime of the running Cell instance.
What the API will never do
- No endpoint returns a stored value.
ribo vault list(and the underlyingGET /v1/vault/...API) return key names only. From the outside, the vault is write-only: you can set, list, and delete — never read back. - Values are never logged — not in request logs, not in the audit trail.
Access control and auditing
- Vault routes require authentication, and API tokens must carry the explicit
vault:read(list names) orvault:write(set/delete) scope. A token minted for deploys withcells:writealone cannot touch the vault. - Every vault set and delete is recorded in the platform audit trail — which account, which actor (interactive session vs. API token), which cell and key, and when. The recorded event contains the key name only, never the value.
The honest trust boundary
The vault is designed to protect you against the realistic, common failure modes — but no hosted secret store can protect a secret from the platform that ultimately has to hand it to your running code. Concretely:
The vault protects against:
- Secrets leaking through source control — the value is never in
ribo.toml, the bundle, or git history. - Compromise or exposure of the storage layer — backups and replicas hold only AES-256-GCM ciphertext, and the key lives elsewhere.
- Other tenants — vault entries are scoped to your account; no API path crosses that boundary.
- Read-back by leaked credentials with narrow scopes — a stolen token
without
vault:writecan't overwrite secrets, and no token can read one.
The vault does not protect against:
- The platform operator. Tissue's infrastructure decrypts your secret to hand it to your Cell — that is the service working as designed. Operators do not have a "read secret" tool, but you are trusting the platform, exactly as you do with the equivalent feature on any hosted serverless provider.
- Your own Cell's code. Anything running in the Cell can read
env.API_KEYand send it anywhere. Review your dependencies; a Cell's secret is only as safe as the code you deploy. - Anyone allowed to deploy your Cell. Deploy rights effectively include the ability to exfiltrate the injected secrets from inside the Cell.
If your threat model cannot accept the platform seeing a secret at dispatch time, keep that secret outside Tissue and have your Cell talk to it indirectly (for example, a backend you host that holds the credential itself).
Rotating a secret
Vault values are injected when a Cell instance is loaded, so a rotation is two steps — the second one matters:
ribo vault set my-cell API_KEY # 1. store the new value
ribo deploy # 2. redeploy so running instances reload it
Without the redeploy, an already-running Cell instance keeps the old value
until it is next evicted. Rotate immediately if a token with vault:write
scope leaks, a teammate with access leaves, or an upstream provider reports a
credential exposure.
Practical recommendations
- Set before deploy. A
vaultbinding whose value is missing at deploy time is stored as a placeholder, and your Cell will see the placeholder string — a silent failure. Runribo vault setfirst. - Use narrow API tokens in CI. A pipeline that only deploys needs
cells:write; give itvault:writeonly if it actually rotates secrets. - Prefer vault over
${VAR}substitution for anything sensitive. Environment substitution intype = "text"bindings is for non-secret, environment-specific values — the resolved value still travels inside the deploy bundle. - One credential per key. Don't pack JSON blobs of multiple credentials into one entry; separate keys rotate and audit independently.