What the vault stores, and for how long

The complete inventory of what reaches an Agent Vault server, what it can and cannot read, what is retained, and what a deletion actually deletes.

Why this document exists

"Zero-knowledge" is a claim about the secret. It is not a claim that the server knows nothing — a server that knew nothing could not enforce access control or answer who opened what. So the useful question is not whether the server holds data, but exactly which data, and this page answers it in full rather than in summary. If you are filling in a processing agreement, everything you need is here.

The short version: the server holds ciphertext it has no key for, plus the metadata required to decide who may fetch that ciphertext, plus an audit chain of what happened. Nothing else. The one category that is absent is the one that usually matters most: no plaintext secret ever reaches it, and there is no code in the binary that could open one — the build fails if the server links a sealing library at all.

The inventory

What the server cannot read

DataWhat it is
Version bodiesThe sealed secret. An age envelope with exactly one X25519 recipient — the item's key, which lives only on keyholders' devices and in Emergency Kits.
Item sealed.jsonThe item's own metadata (labels, field schema, notes) sealed to the same recipients, byte-for-byte as the client signed it.
Grant wrapsA keyholder's copy of an item key, sealed to that keyholder. For an agent, only half of a key-encryption key.

The server stores these as opaque bytes. It validates their shape — one recipient stanza, no scrypt, within the size caps — and nothing else. That validation is why a recipient cannot be injected into an envelope on its way past, and it needs no key to perform.

What the server can read, and needs to

DataWhy it existsRetention
Owner record: the OIDC subject that owns the namespaceEvery request is authorised against itLife of the owner
Vault and item ids, creation times, item stateAddressing and listingLife of the item
Keyholder records: id, kind, public keys, label, the owner's signature over themDeciding who may fetch which wrap. Signed by the owner, so the server cannot add oneLife of the keyholder, then a signed tombstone
Per-item manifest: seq, recipients, policyHash, minimum envelope version and KDF profileThe hash chain a client checks to detect a rewritten historyPermanent — the chain is the evidence
Invites: id, expiry, uses remaining, the depositor's public verifier, the item it targetsRedeeming a deposit exactly as many times as the owner allowedUntil expiry or exhaustion, then the record remains as a spent marker
Agent access policies: item, expiry, uses budget, host bindingEnforcing the bound the owner setLife of the policy; expiry is mandatory and capped at 30 days
Audit chainSee belowPermanent by design

What a deposit records about the depositor

Three fields, and it is worth being precise about each because a depositor is usually not the owner's user and has agreed to nothing:

  • ipHash — an HMAC of the client address under a server-held key, truncated to 12 bytes. The address itself is never written down, and the hash is not reversible to one. It exists so an owner can see that two deposits came from the same place without the server holding where that is.
  • ua — the User-Agent string as sent.
  • clientTrust — which client sealed this version: origin:<url> for a browser page the owner's origin allowlist names, origin-unlisted for any other browser origin, none for the CLI. This identifies a client; it does not authenticate one. A forged Origin header can only claim to be further inside the boundary, which is why the field is evidence for an audit rather than an access-control input.

No name, no email address, no cookie, no fingerprint beyond the above. The deposit page sets no analytics and loads nothing from a third party.

Retention, and why almost nothing expires

Nothing is deleted as a side effect. That is a design rule, not an implementation detail, and it runs the opposite way from most retention advice for a specific reason: in a system where the server cannot read the data, silent deletion is indistinguishable from silent corruption, and neither the owner nor an auditor could tell which had happened.

So every removal is an explicit, signed, recorded act:

  • vault item rm is a soft delete with a matching restore.
  • Revoking a keyholder writes a signed tombstone that says who revoked what and when. The grant goes; the tombstone stays. For an agent this also destroys the server's half of the key-encryption key, which is what makes a wrap the agent already fetched stop opening.
  • Purging version bytes requires a signed purge record that outlives the bytes it destroyed. The version's address then answers 410, permanently. A 404 would say "never existed"; 410 says "was here, was destroyed, here is the record of by whom".

The audit chain is never truncated or rotated away. It is hash-chained, so removing an entry is detectable by anyone holding a later hash — which means truncation-as-retention is not available even in principle.

Three things do expire on their own, all of them capabilities rather than data: invites (at the owner's chosen expiry or when uses run out), agent access policies (mandatory, at most 30 days), and the challenge nonces behind a deposit or an agent's proof-of-possession, which are short-lived by construction. The one release that actually hands over key material is single-use durably: the releaseId is minted by the server and recorded, so a replay answers 409 whether or not the process has restarted since.

Erasure requests

A subject-erasure request against a vault has an unusual shape, and it is better to know that before you sign a processing agreement than after.

What can be erased: any version, item, or keyholder record, by the acts above. The bytes go; a signed record of the removal remains.

What cannot: the audit chain's own entries. Their content is ids, times, and event types — an ipHash is the only quasi-identifier in it — but they cannot be selectively removed without breaking the chain that makes every other guarantee checkable. Plan for this the ordinary way: if a deposit must be attributable to a person, that attribution belongs in your own records, not in the vault's.

What is already unavailable to you: the plaintext. If a depositor asks what their secret was, no one on the server side can answer — including you. That is the product working, and it is worth saying out loud in an agreement rather than discovering during an incident.

If you are running your own

The data volume is the store. Backing it up backs up ciphertext, and restoring an old snapshot restores an old audit chain — which a keyholder's pinned high-water mark will notice, because a restored snapshot legitimately looks like a rollback. Revocations survive that: a restored snapshot does not un-revoke anything, by construction.

Two keys sit beside the data, and neither can open an envelope:

  • server/nonce.key, on the volume, minted on first start. It is what ipHash is computed under, so it is also what keeps that value comparable across restarts. It travels with a backup, which is what you want — restoring a volume without it would silently make every historical ipHash incomparable to a new one.
  • VAULT_KEK, deliberately not on the volume, because it is what webhook signing secrets are encrypted under at rest. Storing it next to what it protects would defeat the point. Keep it with your other service secrets; leave it unset and the webhook routes answer 503 rather than storing a secret in the clear.

Losing either puts no stored secret at risk. Neither holds an item key.

See self-hosting for the operational side.