Cells/Vault Security Model

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

  1. You set it. ribo vault set my-cell API_KEY reads the value with hidden input (or from an environment variable) and sends it to the management API over HTTPS. It never touches ribo.toml, your git history, your shell history, or the deploy bundle.
  2. 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.
  3. 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.
  4. 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 underlying GET /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) or vault:write (set/delete) scope. A token minted for deploys with cells:write alone 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:write can'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_KEY and 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 vault binding whose value is missing at deploy time is stored as a placeholder, and your Cell will see the placeholder string — a silent failure. Run ribo vault set first.
  • Use narrow API tokens in CI. A pipeline that only deploys needs cells:write; give it vault:write only if it actually rotates secrets.
  • Prefer vault over ${VAR} substitution for anything sensitive. Environment substitution in type = "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.