Svault
The principled way to give cooperative AI agents secret access.

Svault is a secret access layer for AI agents, written in Rust. It sits between an agent and your credentials and makes every request structured, policy-gated, and audited: the agent must say which secret, in what scope, and why — and a sensitive request is scored by an AI judge before any value is returned.
The boundary, stated up front. Svault is built for cooperative and semi-trusted agents. It encrypts secrets at rest and gives you an enforced, tamper-resistant gate over agent access plus an audit trail. It is not a sandbox against a hostile process running as your own user — that process can read an unlocked session directly. Svault raises the bar for agents that mostly play by the rules and gives you the audit trail when one doesn't; it does not pretend to contain a determined local attacker. If that distinction matters to you, you're exactly who it's for. See the threat model.
Why not just 1Password / Infisical / HashiCorp Vault? Those treat an AI agent like any other client. Svault makes the agent path first-class — structured requests, per-secret policy and tiers, an AI judge for sensitive reads, and an audit record stamped with the caller's real (un-forgeable) UID.
flowchart LR
H["Human"] -->|"svault secret get<br/>(passphrase)"| V["Svault"]
A["AI Agent"] -->|"svault get<br/>scope + reason"| P["Policy engine"]
P -->|"allow / deny + audit"| V
V --> E["vault.enc<br/>AES-256-GCM"]
Documentation
| Guide | What's inside |
|---|---|
| Installation | crates.io, from source, supported platforms |
| Interactive mode (TUI) | The full-screen dashboard and keybindings |
| Command reference | Every subcommand and flag |
| End-to-end walkthrough | Full flow: create → classify → judge → gated get, with real model output |
| Policy engine | The agent path (via the MCP server), scopes, tiers, audit |
| MCP server | svault mcp — gated secret access for AI agents (Claude Code, Cursor) |
| Recovery & portability | Recovery code for a lost passphrase, export/import bundles |
| Daemon | Optional Unix daemon — keys in memory, auto-lock, daemon start/stop/status/doctor |
| Architecture | How it works, on-disk layout, storage and vault naming, auth methods |
| Security model | Crypto, memory safety, what's safe to commit |
| Security review & audit | Independent review per release + the bulletproofing process |
| QA checklist | Scenario-driven manual test pass (CLI / TUI / MCP) before a release |
| Roadmap | Where Svault is headed |
| Changelog | What's shipped, version by version |
Quick start
# Install
# 1. Create an encrypted vault (interactive: name, agents, auto-lock,
# default tier, AI judge). On first run you set one master passphrase — it
# unlocks every vault. Prints a one-time recovery code — save it.
# 2. Add secrets — also classifies each one (scope + sensitivity tier) for the gate
# 3. Unlock for your session — one master passphrase opens every vault
# 4. Use secrets without re-entering the passphrase
# 5. Lock when done
Or just run svault with no arguments for the interactive TUI.
Run svault with no subcommand to open the full-screen terminal UI:
Browse all vaults (with live lock state), c create, u unlock / l lock, s edit settings, shift-J manage the AI judges (create or unlock the keyring, toggle the global on/off switch, add/edit/view judges with their model/thresholds/criteria, set the default judge, set/clear a judge's API key, live test, remove a judge), and — once a vault is unlocked — a add, c classify (tier/scope/reason/description), view, and d delete secrets, with each secret's classification shown inline. The TUI reuses the cached session key, so an unlocked vault is never re-prompted. Every subcommand still works for scripting.
Full keybindings → docs/tui.md
svault secret get is the human path — passphrase, no questions asked. The agent path is a structured request that an AI must justify, enforced inside the daemon that holds the key — not advisory. Agents reach it through the MCP server (svault mcp, see mcp.md); the svault get CLI below is the same gate but is deprecated (kept for illustration). There is no unguarded read path, and every decision is audited with the connecting process's peer UID.
# deprecated CLI form of the agent gate (new integrations use the MCP server)
flowchart TD
REQ["svault get"] --> ID["Identify caller"]
ID --> RSN{"Reason valid?"}
RSN -->|no| DENY["Deny + audit"]
RSN -->|yes| CAP{"Caller holds scope<br/>& matches secret?"}
CAP -->|no| DENY
CAP -->|yes| RATE{"Within rate limit<br/>& no burst?"}
RATE -->|no| DENY
RATE -->|yes| TIER{"Tier?"}
TIER -->|low| ALLOW["Return value + audit"]
TIER -->|medium / high| JUDGE{"AI judge"}
JUDGE -->|allow| ALLOW
JUDGE -->|deny| DENY
The AI judge. For medium/high-tier secrets, Svault asks a fast LLM — via your OpenRouter account — whether the stated reason plausibly justifies the request. This is the behavioural gate that makes Svault AI-aware. You can define multiple named judges, each with its own model, thresholds, and free-text criteria, pick a default, and assign one per vault.
Everything that gates access is AES-256-GCM encrypted at rest — per-secret classification (scope/tier + an optional description the judge weighs against the reason) and caller rules live inside vault.enc; the judge registry and its API keys live in a separate encrypted keyring (.svault/keyring.enc). There are no plaintext config or key files. Because the policy is unreadable at rest, an agent can't study it to craft a passing request — and a denied svault get returns only a generic message, with the real reason recorded in the audit log for you.
Full pipeline, tiers, judge setup → docs/policy-engine.md
svault create prints a one-time recovery code — a 160-bit second keyslot into the vault, used if you lose the master passphrase. It's shown once and never stored in plaintext; keep it in a password manager.
The bundle carries no machine-specific state and every byte is encrypted or signed — safe to move between machines (same major Svault version).
Recovery code + export/import → docs/recovery.md
Every vault is stored locally — an encrypted vault on this machine. The backend is recorded in meta.yaml as storage: local and shown as a local: prefix everywhere a vault is listed. Vault names must be unique.
Details → docs/architecture.md
| Property | Implementation |
|---|---|
| Encryption | AES-256-GCM (authenticated) |
| Key derivation | Argon2id (64 MB, 3 iterations) — GPU-resistant |
| Unlock | One master passphrase wraps a random per-vault data key (keyslot model) — unlock once, every vault opens |
| Policy & judge config | Encrypted at rest — the policy in vault.enc, the judge registry + API keys in keyring.enc. No plaintext config or key files |
| Metadata integrity | HMAC-SHA256 — tampering with the public meta.yaml is detected |
| Memory safety | VaultKey + secrets derive ZeroizeOnDrop — wiped on drop |
| Session / on-disk files | Owner-only (0600), written atomically |
| Vault file | Safe to commit — encrypted at rest, useless without the master passphrase |
One master passphrase is the only key you type — it wraps each vault's random data key, so unlocking once opens everything. The recovery code is a second keyslot into a vault if you lose the master.
Threat model + on-disk layout → docs/security.md
Every 0.x.0 release goes through an independent security review + bulletproofing pass — see docs/security-review/.
flowchart TD
U["AI Agent"] -->|"svault get (scope + reason)"| D["Svault daemon<br/>(enforced gate)"]
D --> POL["Policy checks<br/>reason → capability → rate limit · burst"]
POL --> TIER{"Sensitivity tier"}
TIER -->|low| OUT["audit (peer UID) → value"]
TIER -->|medium / high| JUDGE["AI judge (OpenRouter)"]
JUDGE --> OUT
OUT --> ENC["(.svault/<vault>/vault.enc<br/>AES-256-GCM encrypted)"]
Enforced-engine details, full layout → docs/architecture.md
Screenshots
Full set with capture notes in
docs/screenshots/.
Roadmap
| Milestone | Status | What |
|---|---|---|
| Foundation | Shipped | Local AES-256-GCM vaults (Argon2id), the interactive Ratatui TUI, recovery code + encrypted export/import, and the Unix daemon (keys in memory, auto-lock) |
| Enforced policy + AI judge | Shipped | Daemon-enforced policy engine (peer-UID-audited) — reason, scopes, tiers, rate limit, burst — plus the AI judge (OpenRouter) gating medium/high-tier secrets |
| Everything encrypted at rest | Shipped | The whole policy surface in vault.enc and all global config + the judge registry (multiple named judges, with API keys) in keyring.enc — nothing abusable in plaintext; per-vault judge assignment; generic caller-facing denials |
| Unified unlock | Shipped | One master passphrase wraps a random data key per store (keyslot model); per-vault passphrases removed and the keyring brought under the master too; svault master init / rekey / status |
| Layered source | Shipped | Source split into a frontend-agnostic core plus cli / tui / daemon frontends (a library crate), with mcp / gui placeholders — structural only, so future frontends reuse core |
| Local MCP | Shipped | svault mcp — a local stdio MCP server exposing gated svault_get_secret / svault_list_vaults to AI agents; serves only unlocked state, never the passphrase, with a capability descriptor that advertises the request interface, not the decision criteria |
| Hardware-key unlock + hardening | Shipped | YubiKey (FIDO2 hmac-secret) unlock — an alternative keyslot over the master key (passphrase or touch, not 2FA); a 6-hour re-auth cap on every unlock path; first-run onboarding + an app-level TUI sign-in / logout; storage local-only |
| Conditional access + escalation | Shipped | Time-window / required-caller conditions in the encrypted policy; repeated denials seal a secret and escalate to a human (svault pending / approve, TUI A) — agents never self-clear |
| Independent security review | Shipped | Three independent external-model reviews of the full 0.9.9 surface (no Critical/High); the actionable findings fixed before 1.0 (docs/security-review/) |
| 1.0.0 — stable | In review | First stable release: the agent-ready layer consolidated and reviewed, agents on the MCP door, the store at ~/.svault. Install channels (script, Homebrew, Docker) follow post-1.0 |
| Desktop GUI | Planned | Desktop GUI (Tauri) + system tray |
Detail for each milestone lives in the changelog and the full roadmap.
Full roadmap → docs/roadmap.md
Tests
144 tests (plus an #[ignore]d concurrency stress benchmark) cover the crypto core and tamper detection, vault operations, the master keyslot model (wrap/unwrap a data key under the master for both vaults and the keyring, rekey, master recovery-code reset, wrong-master rejection), the policy engine and the enforced daemon gate (including peer-UID-stamped audit and high-tier fail-closed behaviour), the AI judge — run against a fake transport, so the suite never touches the network — and the encrypted-at-rest guarantees for both the policy (vault.enc) and the keyring (keyring.enc).
CI runs the full suite on Ubuntu, Fedora, macOS, and Windows on every push and pull request. A heavier concurrency simulation runs on demand:
Methodology and a recorded run are in docs/security-review/stress/0.6.0.md.
License
Apache 2.0 — see LICENSE.
Built by nim444.