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"]
| 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 — svault get, 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 |
| Roadmap | Where Svault is headed |
| Changelog | What's shipped, version by version |
# Install
cargo install svault-ai
# 1. Create an encrypted vault (interactive: storage, 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.
svault create
# 2. Add secrets — also classifies each one (scope + sensitivity tier) for the gate
svault secret add DB_URL --scope database --tier medium
svault secret add API_KEY --scope api --tier low
# 3. Unlock for your session — one master passphrase opens every vault
svault unlock
# 4. Use secrets without re-entering the passphrase
svault secret get DB_URL
svault secret list
# 5. Lock when done
svault lockOr just run svault with no arguments for the interactive TUI.
Interactive mode (TUI)
Run svault with no subcommand to open the full-screen terminal UI:
svaultBrowse 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
Policy engine — the agent path
svault secret get is the human path — passphrase, no questions asked. svault get is the agent path: a structured request that an AI must justify, enforced inside the daemon that holds the key — not advisory. There is no unguarded read path, and every decision is audited with the connecting process's peer UID.
svault get DB_URL --scope database --reason "run nightly migration" --caller claude-codeflowchart 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.
svault keyring init # create the encrypted keyring (one-time)
svault judge add reviewer # name a judge: model, thresholds, criteria, key
svault judge enable # turn the judge on globallyFull pipeline, tiers, judge setup → docs/policy-engine.md
Recovery & portability
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.
svault recover # enter the code, re-attach the vault to your master
svault export myvault --out vault.json # portable, checksummed encrypted bundle
svault import vault.json # restore on another machineThe 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
Storage
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
Security model
| 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/.
Architecture
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
| 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 | Next | Time-window / caller conditions in the encrypted policy; brute-force / anomaly seals a secret and escalates to a human (agents never self-clear) |
| Independent review + install channels | Target | A final independent review of the full agent-ready surface and install channels (script, Homebrew, Docker), then the first stable release |
| 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
cargo test131 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:
cargo test --release daemon_stress_simulation -- --ignored --nocaptureMethodology and a recorded run are in docs/security-review/stress/0.6.0.md.
Apache 2.0 — see LICENSE.
Built by Soluzy.
