
credential isolation for AI agents. Agents never see real API keys - structural guarantee, not policy.
A credential firewall for AI agents.
The headline claim is structural, not policy: agents receive placeholder tokens, never real API keys. The real key crosses only one network seam — inside the wardn proxy, on its way to the upstream API — and is stripped from responses before they reach the agent. Logs, environment, LLM context windows, scratch files, and shell history hold only placeholders.
agent process OPENAI_KEY=wdn_placeholder_a1b2c3d4e5f6g7h8 (useless)
agent logs Authorization: Bearer wdn_placeholder_a1b2... (useless)
LLM context wdn_placeholder_a1b2c3d4e5f6g7h8 (useless)
wardn proxy injects the real key in-flight, single seam (deleted on response)
~/.vibeguard/vault.enc AES-256-GCM(Argon2id(passphrase)) (encrypted at rest)
This is the load-bearing claim and it's defensible today against agent compromise, prompt injection, log theft, and skill exfiltration. Read docs/THREAT-MODEL.md for the honest split between what is covered and what isn't — including the tier where the stronger "host compromise leaks nothing" claim becomes reachable.
The vault itself (encrypted at rest, passphrase-derived key) is a real component and the reason the firewall can run on a single machine. The upcoming docs/HOSTED-TIER.md tier additionally wraps the proxy in a confidential-compute enclave so even a fully compromised VPS cannot read the key.
Every AI agent framework today stores API keys in environment variables or
.env files. A compromised agent, malicious skill, commodity stealer, or
prompt injection exfiltrating Authorization: Bearer sk-... from an LLM
log gets full access to your credentials.
~/.env → OPENAI_KEY=sk-proj-real-key # plaintext, readable by anyone
agent context → "Use OPENAI_KEY=sk-proj-real-key" # leaked into LLM context window
agent logs → Authorization: Bearer sk-proj-... # sitting in log files
wardn hands agents a useless placeholder string and removes the real key from every surface it can reach. Real keys are injected at the network layer — a single seam — and stripped from responses before they reach the agent.
agent environment → OPENAI_KEY=wdn_placeholder_a1b2c3d4e5f6g7h8 (useless)
wardn vault → OPENAI_KEY=sk-proj-real-key (encrypted at rest)
upstream request → Authorization: Bearer sk-proj-real-key (network transit only)
upstream response → ...real keys stripped, placeholders returned... (re-injected on the way back)
agent logs → Authorization: Bearer wdn_placeholder_a1b2... (useless)
LLM context window → wdn_placeholder_a1b2c3d4e5f6g7h8 (useless)
flowchart TB
subgraph Agent["AI Agent Process"]
A1["Agent Code"]
A2["ENV: OPENAI_KEY=wdn_placeholder_a1b2..."]
end
subgraph Wardn["wardn daemon · localhost:7777"]
direction TB
P["HTTP Proxy"]
MCP["MCP Server\n(stdio)"]
subgraph Pipeline["Request Pipeline"]
direction LR
S1["Identify\nAgent"] --> S2["Resolve\nPlaceholder"] --> S3["Check\nAuth"] --> S4["Rate\nLimit"] --> S5["Inject\nReal Key"]
end
subgraph ResponsePipeline["Response Pipeline"]
direction RL
R1["Strip Real\nKeys"] --> R2["Replace with\nPlaceholders"]
end
subgraph Vault["Encrypted Vault"]
V1["AES-256-GCM"]
V2["Argon2id KDF"]
V3["Placeholder Map\nper agent × credential"]
end
end
subgraph External["External APIs"]
E1["api.openai.com"]
E2["api.anthropic.com"]
E3["..."]
end
A1 -- "placeholder token\nin headers/body" --> P
A1 -. "MCP: get_credential_ref\nlist_credentials\ncheck_rate_limit" .-> MCP
MCP -. "placeholder token\n(never real keys)" .-> A1
P --> Pipeline
Pipeline --> External
External --> ResponsePipeline
ResponsePipeline -- "response with\nplaceholders only" --> A1
Pipeline <--> Vault
ResponsePipeline <--> Vault
style Agent fill:#1a1a2e,stroke:#e94560,color:#fff
style Wardn fill:#0f3460,stroke:#16213e,color:#fff
style Pipeline fill:#16213e,stroke:#e94560,color:#fff
style ResponsePipeline fill:#16213e,stroke:#e94560,color:#fff
style Vault fill:#1a1a2e,stroke:#00d2ff,color:#fff
style External fill:#0a0a0a,stroke:#533483,color:#fff
Agent sends request with placeholder in Authorization header
│
▼
┌─────────────────────────┐
│ wardn proxy │
│ localhost:7777 │
│ │
│ 1. Identify agent │
│ 2. Resolve placeholder │
│ 3. Check authorization │
│ 4. Check rate limit │
│ 5. Inject real key │
│ 6. Forward request │
│ 7. Strip key from resp │
│ 8. Return to agent │
└─────────────────────────┘
│
▼
External API (only place real key exists in transit)
| Tier | Where | What holds |
|---|---|---|
| Self-host (today) | your laptop, your VPS, CI | Encrypted-at-rest vault, firewall claim against agents. Does not defend against root on the host. |
| Hosted (upcoming) | wardn-managed or BYO-cloud | Confidential-compute enclave (Nitro / SEV-SNP) + remote attestation + encrypt-to-the-proxy flow. Real "host compromise leaks nothing" claim. |
The self-host tier is the load-bearing claim and ships today. The hosted tier is the strict-upgrade path: it costs money and operational complexity, and its design is in docs/HOSTED-TIER.md. Full honest inventory of what is and isn't covered:
👉 docs/THREAT-MODEL.md — covers / does-not-cover table, "no software vault eliminates host compromise" called out plainly, and the upgrade path.
# Prebuilt binary (Linux/macOS, amd64/arm64), checksum-verified
curl -sSf https://raw.githubusercontent.com/rohansx/wardn/main/install.sh | sh
# or from crates.io
cargo install wardn
# or Homebrew, once the tap is published (see Formula/wardn.rb)
brew install rohansx/wardn/wardn
# Create an encrypted vault and store your keys
wardn vault create
wardn vault set OPENAI_KEY
wardn vault set ANTHROPIC_KEY
# Set up Claude Code integration (one command)
wardn setup claude-code
That's it. Claude Code now uses wardn's MCP server to get placeholder tokens instead of reading real keys from your environment.
get_credential_ref → gets wdn_placeholder_a1b2... (not the real key)The real key never enters the agent's memory, logs, or LLM context window.
Once the daemon is up (wardn serve, or spawned by wardn run), open
http://127.0.0.1:7777/ui in a browser. A read-only, local-only view of: