Vault

The local, sealed store for your agents' wallets and secrets.

The vault is a local-first store on your machine for wallets and secrets, encrypted at rest. Agents never read from it directly — they act through a warrant.

Unlock modes

Set with warden init --mode:

  • password — a scrypt-derived key; fully offline, no chain needed.
  • password+seal — password and the Seal factor (two-factor; the Seal factor is owner-gated on-chain).
  • seal — passwordless; the owner signs at open.

Seal modes (password+seal, seal) register the vault on-chain and recover the Seal factor via an owner-signed cert dance — which is also what makes the on-chain kill-switch bite.

Owner

Every vault has an owner Sui address, set at init with --ownerrequired for every mode, with no silent default. It's the one identity that:

  • revokes the vault or a warrant on-chain (the kill-switch),
  • approves high-risk actions (owner-wallet approvals verify against it), and
  • for password+seal / seal, signs the unlock.

Warrants inherit it. Use --owner keystore (or self) for your sui keystore active address, or pass an explicit wallet/zkLogin address. An owner-wallet-offchain approver can differ from the vault owner via config set --approval-owner, which overrides it for approvals only.

For seal modes, choose an owner you can sign with — a keystore key (signed in-process) or a wallet you connect in warden ui. An owner you don't control can never unlock the vault.

Vault file structure

The vault is a directory of plain files (default ~/.warden/). Public metadata is readable; all key material is sealed.

~/.warden/
├─ config.json            public · vault-key signed (kdf_salt, owner, approval, rpc)
├─ vault_key.json         the vault's ed25519 key — seed sealed under the KEK
├─ wallets/<id>.json      wallet index + sealed_passphrase (sealed under the KEK)
├─ wallet_keys/wallets/   OWS keystore — the mnemonic, encrypted under the wallet passphrase
├─ secrets/<id>.json      master_dek (under the KEK) + payload (the credential, under the DEK)
├─ warrants/<hash>.json   per-token key copies + scope, policy hash, on-chain refs
├─ policies/<hash>.json   content-addressed policy documents
├─ pending/ approvals/    approval markers
└─ audit/ logs/           audit trail

<hash> is sha256(token) for warrants and the content hash for policies; <id> is a uuid.

How it's encrypted

Everything sensitive is sealed under a KEK (key-encryption key) derived from your unlock factors:

  • passwordscrypt(password, kdf_salt) — OWS Keystore-v3 params (N=2¹⁶, r=8, p=1).
  • password+sealH(scrypt(password) ‖ seal_factor); sealH(seal_factor). The Seal factor is recoverable only via the owner-gated on-chain decrypt, so an on-chain revoke destroys the KEK.

Wallets are two layers. The mnemonic lives in the OWS keystore under wallet_keys/, encrypted under a per-wallet passphrase (AES-256-GCM, scrypt). That passphrase is itself sealed under the KEK in the wallet's index file (sealed_passphrase). To sign: KEK → unseal passphrase → decrypt mnemonic → sign in-process. Only the public chain_id + address per account are stored in the clear.

Secrets use an envelope. A random data-encryption key (DEK_master) encrypts the credential (payload); DEK_master is then wrapped under the KEK (master_dek). To read: KEK → unwrap DEK_master → decrypt payload. Only name, type, and host_pattern are public.

How secrets are contained

An agent never has your password or the KEK. When you issue a warrant, Warden writes a second copy of just that warrant's scoped keys into warrants/<hash>.json, each wrapped under HKDF(token) instead of the KEK:

  • wallet_tokens — the wallet passphrase, per scoped wallet.
  • secret_deks — the DEK_master, per scoped secret.

So the bearer token alone opens exactly its slice of the vault, and nothing else. Revoke the warrant and that copy is gone — the agent holds the token, never the KEK, a passphrase, or a raw key.

A credential is decrypted only for the instant it's used. The gateway resolves it from the token (no KEK), injects it into the upstream request, and relays the response — the agent, which holds only the token, never sees the credential. The decrypted bytes (and the intermediate DEK_master) live in mlocked memory — never swapped to disk — and are zeroized the moment they drop. Nothing decrypted is ever logged or returned by the local API, which exposes only name, type, and host_pattern. The process disables core dumps and debugger (ptrace) attach so the material can't be lifted out of memory; the long-lived gateway also zeroizes any cached keys on SIGTERM / SIGINT / panic before it exits.

How wallets are contained

Warden custodies the signing key in-process (via the OWS signer): the mnemonic is never written decrypted and never handed to the agent. To sign, Warden decrypts the keystore in memory, signs in-process, and returns only the signature — the seed never leaves the process. The wallet passphrase and the derived 32-byte seed are held in mlocked, zeroize-on-drop buffers and wiped the moment the key is built. The same hardening applies: no core dumps, no ptrace, and the gateway zeroizes cached keys on exit. Short-lived CLI commands like warden sign cache nothing — their key material is transient and wiped on drop.

Decrypted keys and credentials live only in locked memory and are wiped on drop — never written to disk, never returned to the agent.