Agent vault: secrets your AI agent uses but never sees
An agent vault is a set of credentials scoped to one named AI agent. The agent uses what you send it — deploys with the API key, queries the database, signs the request — without the plaintext ever being handed back to it. The vault never returns it as tool output, which is the step that would otherwise drop your key into the model's context and the transcript. Each agent sees only its own vault, and you can cut its access off whenever you want.
That is the whole point of the shape. Ask a secret manager for a credential and it gives you the credential; when the thing asking is an agent, that answer is context, and from that moment it can be logged upstream, echoed into a transcript, or talked back out of the agent by a prompt injection. Encrypting at rest doesn't help — the exposure happens at the moment of use. An agent vault closes it by injecting the secret into the process that needs it instead of returning it to the model.
Wundervault goes one step further. Your secrets are encrypted on your side before they are ever uploaded, so what our server stores is ciphertext and we are never given the key that opens it. We couldn't produce your credentials in plaintext if we were asked to — we don't have them. A vault that injects secrets server-side has to produce the plaintext on its own machines to do the injection — the difference is worth understanding before you pick one. And you don't have to believe us: the client crypto is open source, and you can watch your own network traffic and check.
An agent can also open a one-time link the same way a person would, with no account or API key. Know what that does and doesn't buy you: burning destroys the link, not the copy the agent now holds in its context and transcript. It is the right tool for handing an agent its first credential — the onboarding passphrase works exactly this way, and is spent the moment it is exchanged — and the wrong one for a secret the agent will use again. For that, put it in the vault, where it is injected and never returned.
How It Works
Each agent has a dedicated vault scoped only to it. The human approves specific secrets from their own vault and sends them to the agent's vault. The agent accesses those secrets via REST API or MCP — no human login required.
python3 onboard.py "SETUP_URL" — the script verifies its own signature, decrypts the credential blob, registers the profile with the local daemon (stored encrypted on disk), and burns the setup link. The agent never holds credentials directly; the daemon manages them.
GET /agent/vault/secrets to list secrets, then GET /agent/vault/secrets/{id}?purpose=... to retrieve and decrypt each one.
Zero-Knowledge Encryption
The agent vault uses two layers of encryption — the server handles storage but never sees plaintext:
vault_key_for_agent— AES-GCM encrypted with the agent'sencryption_key. The agent decrypts this first to get the vault key. The server never sees the vault key.encrypted_content— each secret value is AES-GCM encrypted with the vault key. The agent decrypts this to get the plaintext secret. The server never sees secret values.- Double decrypt: Agent uses
encryption_key→ decrypts vault key → uses vault key → decrypts secret. Server is never involved in either decryption step.
Secret Tiers
Vault secrets have an access tier that controls how agents retrieve them:
| Tier | Agent flow | Human approval |
|---|---|---|
| Tier 1 (standard) | Agent calls → secret returns immediately | No |
| Tier 2 (approval-gated) | Agent calls → 403 + a pending request it can poll; retry once you approve | Per request, scoped to that agent and secret |
A Tier 2 approval covers one (agent, secret) pair. You choose the scope when you grant it: one use, a 15-minute window, or a 60-minute window — and you can revoke an open window at any time. Granting requires a fresh biometric or passphrase unlock in the dashboard, so approving is a deliberate act. It is enforced server-side; an agent cannot approve itself, and an open vault grants it nothing.
MCP Server
Use Wundervault as native MCP tools from Claude Code, Cursor, or any MCP-compatible agent. The MCP server handles all encryption and decryption locally — secrets are never returned to the agent in plaintext.
The MCP server and client crypto are open source (AGPL-3.0): github.com/wundervault/wundervault-mcp. The package is published on npm and listed in the official MCP Registry as io.github.wundervault/wundervault-mcp — audit the code that touches your secrets, or verify the build before you install it.
If your agent uses OpenClaw or ClawHub, install the ready-made skill: clawhub.ai/snoweman/skills/wundervault-vault. It wires up all the tools and usage patterns for you.
Install
You normally don't install this by hand — the onboarding command below installs (or upgrades) it automatically. To install or audit it independently:
Available tools
vault_entries_list— list the agent's vault secrets (names and IDs only, no values)vault_entry_get(entry_id, purpose)— retrieve and decrypt a secret. Plaintext is never returned — the agent receives only a burn confirmation.vault_exec(entry_id?, purpose, command, working_dir?, inject_as?, remote_host?)— run any shell command with the secret injected as an env var, locally or on a remote machine via SSH. Shell escape patterns ($(), backticks, bash -c, sh -c, eval) and file-write redirects (>) are rejected before the secret is decrypted.entry_idis optional: omit it to run a remote command using only a vaulted SSH key, with no secret injected.vault_rsync(local_path, remote_host, purpose)— sync a local directory to a remote host with rsync over SSH, using an SSH key fetched from the vault. The key exists in a temp file only for the duration of the transfer and is deleted immediately after.vault_entry_inject_env(entry_id, purpose, file_path, env_key)— write a secret into an allowlisted config file (~/.npmrc,~/.netrc,~/.docker/config.json, project.envfiles). Paths outside the allowlist are rejected.vault_entry_forget(entry_id)— discard a local reference (no-op on the server)
vault_exec — exec_config
Set an exec_config on a vault entry when sending it to an agent (📨 SEND modal). This defines how the secret is injected: which env var, optional setup command (pre_command), and optional teardown (post_command). The agent then calls vault_exec with any command — the vault entry handles injection automatically.
No template knowledge required. Built-in presets in the dashboard: npm, restic, git, AWS. Or set a custom env key and commands.
If the agent needs to override the injection recipe for a specific call, it can pass inject_as: { env_key, pre_command?, post_command? } directly.
vault_exec — remote execution via SSH
Pass a remote_host block to run the command on a different machine. The MCP server SSHes to the host and pipes the secret injection via SSH stdin — the secret is set as an env var inside the remote shell and never exists as a local env var, never appears in SSH arguments, and requires no AcceptEnv or SendEnv configuration on the remote host.
Use ssh_key_entry_id to load the SSH private key from the vault — the key is fetched, used for the connection, and never written to disk or exposed to the agent. This is the recommended approach: the agent cannot SSH directly and all remote execution is forced through the controlled vault_exec path.
ssh_key (a filesystem path) is also accepted as a fallback. Omit both to use the SSH agent. The same security properties apply as local exec: shell escape patterns are rejected before decryption, output is scrubbed, and the secret buffer is zeroed after use.
.env files containing plaintext secrets. Vault-level controls cannot restrict what an agent reads once it has shell access. Recommended: do not store plaintext secrets in .env files on machines agents can SSH into. Use vault_entry_inject_env at container startup to inject secrets into the process environment without writing to disk, and use ssh_key_entry_id to prevent agents from holding SSH keys directly. See the Security White Paper §1.6 for the full threat model and mitigations.
vault_exec, the secret is injected as a named environment variable, the parent process buffer is zeroed immediately after the subprocess spawns, sensitive parent-env keys are stripped from the child environment, and command output is scrubbed before being returned.
Tier 2 enforcement
Tier 2 access is enforced server-side, per request — there is nothing for the agent to manage locally. A Tier 2 secret is only released while you have a dashboard session with a biometric (WebAuthn) unlock; otherwise the agent's request is refused and the attempt is recorded in the audit log as tier2_blocked. Tier 1 secrets execute without the extra gate.
Setup
Account owner: create your account, register the agent in the dashboard, and copy the onboarding command it generates (a one-time setup URL is embedded). Paste it to your agent.
The script verifies its own signature, installs or upgrades @wundervault/mcp-server automatically, exchanges credentials with the vault, registers your profile with the local daemon (and starts it), and writes a token file to ~/.wundervault/agents/AgentName.token. It prints a config snippet to add in the next step.
The script prints a block like this — copy it into your own framework config. Each agent manages their own config entry; the onboard script does not write to any framework config automatically.
Config file locations by framework:
- Claude Code —
~/.claude.json→mcpServers - Grok Build —
~/.grok/mcp.json→servers, or skip the file and rungrok mcp add wundervault --command "wundervault-mcp" --env WUNDERVAULT_AGENT_NAME=AgentName - OpenClaw —
~/.openclaw/openclaw.json→mcp.servers - Windsurf —
~/.codeium/windsurf/mcp_config.json→mcpServers - Hermes —
~/.hermes/config.yaml→mcp_servers
No token or API key belongs in the config. The token is auto-discovered from the file the onboard script wrote.
Claude Code watches its config file and picks up the new entry automatically — vault tools appear within seconds of adding the snippet, no restart needed.
Grok Build — confirm the server is wired up with grok mcp test wundervault; it reads MCP servers configured the same way Claude Code's are.
OpenClaw, Hermes, Windsurf — restart the gateway process after adding the snippet. Vault tools will be live on the first call.
Credentials are stored encrypted by the local daemon — no plaintext files, no API keys in config. WUNDERVAULT_AGENT_NAME identifies which agent profile the daemon serves to the MCP server at runtime.
Reinstall / Repair
Use this when vault tools stop working — 502 errors, auth failures, or after a credential rotation.
Option A — Repair in place (recommended)
If the agent name and vault URL haven't changed, generate a new setup link from Settings → Agents and run:
Replaces only the named agent's profile and notifies the running daemon. Other agents are untouched.
Option B — Fresh agent name
- Run
onboard.pywith the new setup URL — no flags needed - Update
WUNDERVAULT_AGENT_NAMEin your framework's MCP config to the new name - Restart your gateway if required
The old agent profile remains until you revoke it from the dashboard (Settings → Agents).
Uninstall
Remove one agent (keep others running)
- Revoke from dashboard: Settings → Agents → Revoke
<AgentName> - Remove token and socket:rm ~/.wundervault/agents/<AgentName>.token && rm -f ~/.wundervault/agents/<AgentName>.sock
- Remove the
wundervaultentry from your framework's MCP config - Restart your agent session
Remove everything (last agent on machine)
Troubleshooting
502 error from vault tools
Means the MCP server is hitting a stale vault URL. Diagnose step by step:
Step 1 — confirm which agent name is in use:
Step 2 — check daemon socket:
Step 3 — check agent socket:
If daemon is running but agent socket is missing → re-run onboard.py --repair.
Gateway restart
After updating MCP config, these frameworks require a gateway restart before vault tools appear. Claude Code picks up changes automatically.
Stale lock files
Audit Log
Every agent vault operation is logged. Visible per-agent on the dashboard (click an agent tab → Access Log) and in the Usage Audit Log section below the vault.
| Field | Description |
|---|---|
| Agent | Agent name + fingerprint (first 6 chars of agent ID) — e.g. claude [a3f2b1] |
| Secret | Which secret was accessed |
| Action | vault_listed, vault_retrieved, sent_to_vault, agent_revoked, tier2_blocked |
| Purpose | Agent's declared reason for access (required, max 200 chars) |
| IP | Where the request originated |
| Outcome | success, unauthorized, blocked |
Agent fingerprints — each registration generates a unique ID. If you revoke claude and register a new claude, the fingerprints differ ([a3f2b1] vs [c91d40]), keeping the audit trail unambiguous. The fingerprint is shown in the dashboard agent tab header and in every audit log row.
Human vs. Agent Vault
| Human Vault | Agent Vault | |
|---|---|---|
| Auth | Passphrase + optional WebAuthn biometric | Scoped API key in memory |
| Encryption | AES-256-GCM, PBKDF2, true zero-knowledge (independent vault key) | Double-layer ZK: encryption_key → vault key → secret |
| Access | Dashboard, WebAuthn biometric unlock | REST API or MCP |
| Secrets visible | All vault secrets you own | Only secrets explicitly sent to agent's vault |
| Secret tiers | Tier 1 (standard), Tier 2 (approval-gated) | Tier 1 (auto), Tier 2 (approval-gated) |
| Revocation | Delete or rotate secret | Dashboard: revoke agent or remove from vault |
| Audit log | Not applicable | Full log: agent, secret, purpose, IP, timestamp |
Common questions
What is an agent vault?
An agent vault is a set of credentials scoped to one named AI agent. The agent can use the secrets it has been sent — to deploy, call an API, or reach a database — but in the injection flow the plaintext values are not returned to it as tool output, which is what would otherwise place them in the model's context window and the chat transcript. Each agent sees only its own vault, and access can be revoked.
How is an agent vault different from a normal secret manager?
The difference is the integration pattern rather than the storage. A retrieval-based integration returns the plaintext to whatever asked for it; when the caller is an AI agent, that answer arrives as tool output, which is context — it may be logged by the model provider, echoed into a transcript, or extracted by a prompt injection. An agent vault closes that last step by injecting the credential into the process that needs it instead of returning it to the agent. Several conventional secret managers also offer injection-style integrations; the distinction is which pattern your agent actually uses.
Can the vault provider read the secrets?
Ours can't. Secrets are encrypted on your side before they are uploaded, so our server holds ciphertext and is never given a key that opens it — we could not hand over the plaintext if we wanted to, because we do not have it. That is not true of every vault: a design that injects secrets server-side has to produce plaintext inside its own execution environment to do the injection, which means the key is there. Both shapes keep the secret away from the agent. Only one keeps it away from the vendor.
Security Notes
- Credentials are stored by the local daemon in an encrypted profile file (
~/.wundervault/agent-profiles.enc), so no plaintext credentials sit on disk. Be clear on what that key protects against: it is derived from the machine's ID and your uid, with the salt stored alongside the file, so it defends the credentials if the file, the disk or a backup leaves the machine — it does not defend them from a process already running as your user, which can re-derive the key. On the local machine it is your user account that protects these credentials, not the file format. - The API key is provisioned via Wundervault's own one-time secret mechanism — it burns after the onboard script retrieves it, preventing replay
- Tier 2 secrets require biometric unlock even for agents — there is no agent-side bypass
- Revocation is immediate — the agent gets 401 on its next request
- Audit log is append-only — entries are never deleted, even if the agent or secret is removed
- When using the MCP server, secrets are never sent to the agent in plaintext — the tool returns only a confirmation, preventing secrets from appearing in chat or being stored in conversation memory
vault_execaccepts any shell command — shell escape/injection patterns ($(), backticks,bash -c,sh -c,eval) are rejected before the secret is decrypted, preventing prompt injection from escalating to arbitrary command execution with secret privileges. The injection recipe (env var, setup/teardown) lives on the vault entry asexec_config, set once in the dashboard.- The secret is injected as a named environment variable (not a command argument — never appears in process listings); the parent process buffer is zeroed immediately after spawn; sensitive parent-env keys are stripped from the child environment before injection
- When using
remote_host, the secret is piped via SSH stdin — it is never a local env var, never appears in SSH command arguments, and requires no server-side SSH config changes (AcceptEnv/SendEnv). All other exec security properties (pattern rejection, output scrubbing, buffer zeroing) apply equally to remote execution. - Tier 2 access is checked server-side on every request — an agent can never approve itself, and blocked attempts are logged as
tier2_blocked - Secrets in the agent's context or memory cannot be recalled by revocation — limit what you share with agents accordingly