wardn 0.2.0

Credential isolation proxy — agents never see real API keys
Documentation

wardn

Credential isolation for AI agents. Agents never see real API keys — structural guarantee, not policy.

Crates.io 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:

[dependencies]
wardn = "0.1"

Vault Operations

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

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

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):

Tool Description
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

Property Guarantee
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

Attack How wardn stops it
.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

[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 — 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