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.
"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.
| Data | What it is |
|---|---|
| Version bodies | The 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.json | The item's own metadata (labels, field schema, notes) sealed to the same recipients, byte-for-byte as the client signed it. |
| Grant wraps | A 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.
| Data | Why it exists | Retention |
|---|---|---|
| Owner record: the OIDC subject that owns the namespace | Every request is authorised against it | Life of the owner |
| Vault and item ids, creation times, item state | Addressing and listing | Life of the item |
| Keyholder records: id, kind, public keys, label, the owner's signature over them | Deciding who may fetch which wrap. Signed by the owner, so the server cannot add one | Life of the keyholder, then a signed tombstone |
Per-item manifest: seq, recipients, policyHash, minimum envelope version and KDF profile | The hash chain a client checks to detect a rewritten history | Permanent — the chain is the evidence |
| Invites: id, expiry, uses remaining, the depositor's public verifier, the item it targets | Redeeming a deposit exactly as many times as the owner allowed | Until expiry or exhaustion, then the record remains as a spent marker |
| Agent access policies: item, expiry, uses budget, host binding | Enforcing the bound the owner set | Life of the policy; expiry is mandatory and capped at 30 days |
| Audit chain | See below | Permanent by design |
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.
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.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.
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.
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.