# wardn
Credential isolation for AI agents. Agents never see real API keys — structural guarantee, not policy.
[](https://crates.io/crates/wardn)
[](LICENSE)
## The Problem
Every AI agent framework today stores API keys in environment variables or `.env` files. A compromised agent, malicious skill, or commodity stealer 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
```
## The Fix
Wardn vaults credentials with AES-256-GCM encryption and gives agents useless placeholder tokens. Real keys are injected at the network layer — agents never touch them.
```
agent environment → OPENAI_KEY=wdn_placeholder_a1b2c3d4e5f6g7h8 (useless)
wardn vault → OPENAI_KEY=sk-proj-real-key (encrypted)
agent logs → Authorization: Bearer wdn_placeholder_a1b2... (useless)
LLM context window → wdn_placeholder_a1b2c3d4e5f6g7h8 (useless)
```
## How It Works
```
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)
```
## Quick Start
Add to your `Cargo.toml`:
```toml
[dependencies]
wardn = "0.1"
```
### Vault Operations
```rust
use wardn::{Vault, config::CredentialConfig};
// Create an encrypted vault
let vault = Vault::create("vault.enc", "my-passphrase")?;
// Store a credential
vault.set_with_config("OPENAI_KEY", "sk-proj-real-key-123", &CredentialConfig {
allowed_agents: vec!["researcher".into(), "writer".into()],
allowed_domains: vec!["api.openai.com".into()],
rate_limit: Some(RateLimitConfig { max_calls: 200, per: TimePeriod::Hour }),
})?;
// Agent gets a placeholder (not the real key)
let placeholder = vault.get_placeholder("OPENAI_KEY", "researcher")?;
// → "wdn_placeholder_a1b2c3d4e5f6g7h8"
// Different agent gets a different placeholder for the same key
let other = vault.get_placeholder("OPENAI_KEY", "writer")?;
// → "wdn_placeholder_f9e8d7c6b5a4f3e2" (different)
// Rotate the real key — all placeholders keep working
vault.rotate("OPENAI_KEY", "sk-proj-new-key-456")?;
```
### HTTP Proxy
```rust
use wardn::proxy::{self, ProxyState};
use std::sync::Arc;
let state = Arc::new(ProxyState {
vault: Arc::new(RwLock::new(vault)),
rate_limiter: Arc::new(Mutex::new(RateLimiter::new())),
config: WardenConfig::default(),
http_client: reqwest::Client::new(),
});
let app = proxy::build_router(state);
let listener = tokio::net::TcpListener::bind("127.0.0.1:7777").await?;
axum::serve(listener, app).await?;
```
### MCP Server
```rust
use wardn::mcp::WardenMcpServer;
// Serve over stdio (for Claude Code, Cursor, etc.)
WardenMcpServer::serve_stdio(vault, rate_limiter, "agent-id".into()).await?;
```
MCP tools exposed (read-only, no credential values ever returned):
| `get_credential_ref` | Get your placeholder token for a credential |
| `list_credentials` | List credentials you're authorized to access |
| `check_rate_limit` | Check your remaining quota |
## Security Properties
| No credential in agent memory | Agent process only holds placeholder strings |
| No credential on disk in plaintext | AES-256-GCM encrypted vault with Argon2id KDF |
| No credential in logs | Only placeholders appear in any log output |
| No credential in LLM context | Placeholder injected into env, real key at network layer |
| Bounded cost exposure | Token bucket rate limits per credential per agent |
| Credential echo protection | Real keys stripped from API responses before reaching agent |
| Memory safety | `SensitiveString`/`SensitiveBytes` zeroed on drop |
| Atomic persistence | Write-tmp-then-rename prevents vault corruption |
## What This Defeats
| `.env` credential theft | No `.env` files. Keys only in encrypted vault |
| Malicious skill reads `$OPENAI_KEY` | Gets `wdn_placeholder_...` — useless |
| Stealer targets agent config | Finds only placeholder tokens |
| Prompt injection exfiltrates key | Key never in agent context window |
| Agent logs contain credentials | Logs contain only placeholder strings |
| Full agent compromise | Attacker has a useless placeholder |
| Cost runaway from looping agent | Rate limit per credential per agent |
## Configuration
```toml
[warden]
vault_path = "~/.vibeguard/vault.enc"
[warden.credentials.OPENAI_KEY]
rate_limit = { max_calls = 200, per = "hour" }
allowed_agents = ["researcher", "writer"]
allowed_domains = ["api.openai.com"]
[warden.credentials.ANTHROPIC_KEY]
rate_limit = { max_calls = 100, per = "hour" }
allowed_agents = ["researcher"]
allowed_domains = ["api.anthropic.com"]
```
## Architecture
```
wardn/
├── src/
│ ├── lib.rs # Public API, WardenError
│ ├── config.rs # TOML configuration parsing
│ ├── vault/
│ │ ├── mod.rs # Vault CRUD operations
│ │ ├── encryption.rs # AES-256-GCM + Argon2id + zeroize types
│ │ ├── storage.rs # On-disk format (WDNV), atomic writes
│ │ └── placeholder.rs # Token generation, per-agent isolation
│ ├── proxy/
│ │ ├── mod.rs # HTTP proxy server (axum)
│ │ ├── inject.rs # Credential injection into requests
│ │ ├── strip.rs # Credential stripping from responses
│ │ └── rate_limit.rs # Token bucket rate limiter
│ └── mcp/
│ ├── mod.rs # MCP server (rmcp, stdio transport)
│ └── tools.rs # Tool parameter/response types
└── tests/
├── vault_tests.rs # Vault integration tests
└── proxy_tests.rs # Proxy integration tests
```
## Vault File Format
```
Bytes 0-3: Magic "WDNV"
Bytes 4-5: Version (u16 LE)
Bytes 6-21: Argon2id salt (16 bytes)
Bytes 22+: AES-256-GCM encrypted payload (nonce ‖ ciphertext ‖ tag)
```
## Part of VibeGuard
Wardn is the credential isolation layer of [VibeGuard](https://github.com/nicholasgasior/vibeguard) — a security daemon for AI agents. Other modules:
- **Sentinel** — prompt injection firewall
- **CloakPipe** — PII redaction middleware
- **Watcher** — audit log + dashboard
- **Migrate** — credential scanner + auto-import
## License
MIT OR Apache-2.0