The data-access firewall for AI agents.
Your agents' credentials live in an encrypted local vault — and every request an agent makes with them passes through a policy-inspected chokepoint. Agents use credentials without ever seeing them, and everything they do with that access — the queries they run, the data they export, the prompts they send — is inspected, gated, and audited inline, before it leaves your machine.
Single binary. No cloud, no account, no telemetry. Keep whatever LLM gateway you like — Sanctum is the layer beneath it, where the keys live and where data-access policy is enforced.
🤖 AI Agents: See AGENTS.md for machine-readable integration docs.
Quick Start · The Firewall · Transparent Proxy · MCP Integration
The Problem
Every AI coding assistant — Claude Code, Cursor, Windsurf, Copilot — needs API keys. Today, those keys live in .env files:
OPENAI_API_KEY=sk-proj-...
STRIPE_SECRET_KEY=sk_live_...
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI...
This means:
- No access control. Every agent sees every secret. Your research agent can read your production AWS keys.
- No audit trail. No record of which agent accessed which credential, when, or why.
- Plaintext on disk. One compromised tool, one malicious MCP server, one prompt injection — game over.
- No expiration. Secrets live forever in agent memory with no TTL, no lease, no cleanup.
Password managers require human interaction. HashiCorp Vault requires a DevOps team. Neither speaks MCP.
And even a perfect vault only answers who may hold a key — not what they're doing with it. An agent with legitimate database access can still COPY INTO 's3://somewhere-bad'. An agent with a legitimate model key can still paste your secrets into a prompt. Gateways see that traffic but hold your keys in config; vaults hold keys but are blind to traffic; DSPM classifies your data but isn't in the request path.
The Solution
Sanctum sits at the one vantage point that sees everything: the credential-injection chokepoint. Because the vault injects the credential, every byte of agent traffic passes through it in plaintext (TLS terminates to inject — no MITM certificate games), already bound to a cryptographic agent identity, a lease, and a data classification.
Two guarantees follow:
- Agents never see your secrets. Point your agent's SDK base URL at the vault; it injects the real credential on the way through and returns only the API response.
- You see — and gate — everything your agents do. Semantic analyzers classify each request (SQL export? model prompt carrying an API key? bulk egress to an unknown host?) and composable policy rules act inline: flag, alert, require approval, deny, or quarantine (which revokes the lease on the spot).
Features
- Encrypted Vault — AES-256-GCM envelope encryption with per-credential keys. Nothing touches disk in cleartext.
- Agent Identity — Each agent gets an Ed25519 keypair. Authentication via challenge-response, no shared secrets.
- Policy Engine — IAM-style policies with glob matching, rate limiting, and TTL enforcement. Deny by default.
- Transparent Proxy — "Use, Don't Retrieve." Point your SDK's base URL at the vault; it injects credentials server-side and forwards upstream. Agents never see the secret. Supports GET/POST/PUT/PATCH/DELETE/HEAD, bearer/api_key/custom header injection, and HMAC signing.
- Inline Inspection — semantic analyzers classify what agents actually do: SQL operations (select/export/copy…), model prompts (any provider, even behind a gateway), bulk egress, high-entropy uploads — with sensitive-entity detection (secrets, PII) in-flight.
- Data-Access Policy — composable rules over who/what/where/how-much (agent, operation, model, destination host, data classification, volume) with graduated actions: flag → alert → require approval → deny → quarantine (revokes the lease). Exceptions are predicates too:
AND NOT agent matches redaction-bot. Hot reload; no restart. - Dry-Run Before Enforce — replay any draft rule against your agents' recent real traffic and see exactly what it would have blocked, before you enable it.
- Policy Console — live agent-activity feed with matched-rule attribution, a visual rule builder, and one-click rule creation from any event.
- Cryptographic Lease Tokens — Time-bounded access with scope narrowing and cascade revocation.
- SSRF Prevention — Blocks requests to localhost, private networks (10.x, 192.168.x, 169.254.x), and file:// URIs.
- Audit Logging — HMAC SHA-256 hash-chained log. Tamper-evident. Verify integrity with
sanctum audit verify. - MCP Server — Native Model Context Protocol integration. One command:
sanctum mcp serve. - Web Dashboard — Real-time lease monitoring, policy simulator, audit explorer at
localhost:7700. - Credential Scanner — Auto-discovers exposed API keys in
.envfiles, shell configs, and project directories. - Lock/Unlock —
sanctum lockseals the vault instantly.sanctum unlockopens it. - Policy Simulation —
sanctum policy simulatefor dry-run policy evaluation before deploying rules. - Demo Mode —
sanctum demoshows everything working end-to-end without setting anything up.
Install
The fastest path — download the static binary for your platform (no runtime dependencies; the Linux build is fully static and runs on any distro):
curl -fsSL https://raw.githubusercontent.com/SanctumSec/sanctum/main/scripts/install.sh | sh
Or build from source (cargo install --git https://github.com/SanctumSec/sanctum),
or grab a binary from Releases.
macOS & Windows: first-run note (unsigned binary)
The binaries aren't code-signed/notarized yet, so a binary you download in a
browser is quarantined by the OS. The curl | sh installer above avoids
this entirely (curl doesn't quarantine), and running from a terminal works
regardless. If you double-click a browser-downloaded binary:
- macOS: run it from Terminal (works — quarantine only gates GUI launches),
or clear the flag:
xattr -d com.apple.quarantine ./sanctum - Windows: SmartScreen warns on the downloaded
.exe— "More info" → "Run anyway". It may prompt more than once until Windows clears the mark-of-the-web; each time, choose Run anyway. No antivirus quarantine.
Code-signing (Windows) and notarization (macOS) are near-term roadmap items to remove this friction.
Quick Start
# 1. Initialize vault (creates encrypted store, generates keys)
sanctum init
# 3. Store a credential (prompts for value securely)
sanctum store openai/api_key --type api_key
# 4. Register an agent
sanctum agent register coding-agent
# 5. Grant access — let the agent USE the credential against the OpenAI API
# without ever seeing it (the vault injects it and proxies the request).
sanctum policy add coding-access \
--principal "agent:coding-agent" \
--resources "openai/*" \
--actions use \
--allowed-domains "api.openai.com" \
--max-ttl 600
# 6. Start daemon + web dashboard
sanctum daemon start
# Dashboard at http://localhost:7700
Note:
usekeeps the secret inside the vault. If an agent genuinely needs the raw header value (e.g. for a client the proxy can't front), add--allowed-operations http_headerto opt into revealing it — denied by default, since it hands the credential to the agent.
Demo
See everything working end-to-end without setting anything up:
sanctum demo
The Data-Access Firewall
Credentials answer who gets in. The firewall answers what they may do once inside — inline, at the same chokepoint that injects the credential.
Every proxied request is analyzed into a normalized signal — the operation (sql.export, model.prompt, egress.post…), the destination, detected sensitive entities and their classification, volume. Rules evaluate signals and act before the request leaves your machine:
{
"name": "no-secrets-to-models",
"effect": "deny",
"when": { "and": [
{ "operation_glob": "model.*" },
{ "entity_classification_at_least": "confidential" }
]}
}
That rule blocks any prompt to any model provider that carries a confidential entity (an API key, a card number, a PII column) — regardless of which gateway or router the traffic goes through, because the analyzer matches request shape, not hostnames.
Real examples, all enforced inline:
// Agents may only prompt Grok — every other provider is denied
{ "name": "grok-only", "effect": "deny", "when": { "and": [
{ "operation_glob": "model.prompt" },
{ "not": { "attr_glob": { "key": "model", "glob": "grok-*" } } } ]}}
// SQL exports are blocked, except for the ETL fleet
{ "name": "no-sql-export", "effect": "deny", "when": { "and": [
{ "operation_glob": "sql.export" },
{ "not": { "agent_glob": "etl-*" } } ]}}
// Bulk egress anywhere unrecognized → kill the lease immediately
{ "name": "exfil-tripwire", "effect": "quarantine", "when": { "and": [
{ "kind_is": "data_egress" },
{ "volume_bytes_over": 10000000 },
"destination_not_known_platform" ]}}
Author rules in the web console (http://localhost:7700 → Data-Access Policy): watch the live feed of what your agents are doing, click any event to prefill a rule, dry-run it against recent real traffic to see the blast radius, then enable — the engine hot-reloads atomically. Two built-in rules ship enabled: confidential data to non-platform destinations is denied, and unknown destinations are flagged.
Rule evaluation is ~10µs at 1,000 rules — enforcement lives on the hot path without taxing it.
Transparent Proxy — Zero-Code Credential Injection
The fastest way to secure an agent: point its SDK's base URL at the vault. No SDK changes, no wrapper, no envelope — the agent makes normal HTTP calls and the vault injects the credential on the way through.
# 1. Store the credential with its upstream API
sanctum store openai/api_key --upstream https://api.openai.com
# 2. Mint a lease — prints a ready-to-use base URL + session token
sanctum lease mint openai/api_key --agent my-agent
# 3. Point any SDK at the printed base URL. That's it.
export OPENAI_BASE_URL=http://127.0.0.1:7700/api/v1/proxy/t/<lease-id>
curl -H "Authorization: Bearer $SANCTUM_SESSION" $OPENAI_BASE_URL/v1/models
The agent's own auth header is stripped and replaced with the vaulted credential; the upstream's response (JSON or binary, byte-exact) comes back untouched. Kill switch: sanctum lease revoke <lease-id> --agent my-agent — the next request is denied, sub-second, without touching agent code. Leases support TTLs, max-access counts, and per-lease scope binding.
Federated identities (Entra Agent ID, OAuth2)
The vault can hold the root of a token-exchange chain instead of a static key. For Microsoft Entra Agent ID, it stores the agent-blueprint credential and runs the two-leg federated exchange itself — the agent never sees the blueprint secret or the issued tokens, and needs zero MSAL code:
sanctum store entra/graph --entra-tenant <tenant-id> --entra-blueprint <blueprint-client-id> \
--upstream https://graph.microsoft.com
sanctum lease mint entra/graph --agent my-agent \
--scope entra_agent_identity=<agent-identity-client-id> # 1 lease ↔ 1 Entra identity
curl -H "Authorization: Bearer $SANCTUM_SESSION" $BASE_URL/v1.0/users # plain HTTP → Graph
One blueprint credential fans out to many agent identities via lease scopes. Tokens are cached per identity (warm calls cost the same as a static key — measured ≈0.2s vs ≈1.0s cold against live AAD) and refreshed in the background before expiry. Generic OAuth2 client-credentials IdPs (Okta, Auth0, Google, Keycloak) work the same way via --provider-config.
How credential injection works
On each proxied request the vault:
- Validates the agent's lease, identity, and policy permissions
- Decrypts the credential (or brokers a short-lived token, e.g. Entra Agent ID)
- Injects it into the request (bearer token, API key header, HMAC signature)
- Forwards the HTTP call upstream
- Returns the upstream response to the agent
- The credential never leaves the vault process
Supported Injection Methods
| Method | Example |
|---|---|
| Bearer token | Authorization: Bearer sk-... |
| API key header | X-API-Key: ... |
| Custom header | Any header name you configure |
| HMAC signing | Request signature with credential as signing key |
SSRF Prevention
The proxy blocks requests to internal networks:
127.0.0.1,localhost,::110.0.0.0/8,172.16.0.0/12,192.168.0.0/16169.254.0.0/16(link-local)file://,ftp://, and other non-HTTP schemes
MCP Integration
Sanctum works with any MCP-compatible tool. Add this to your editor's MCP config:
{
"mcpServers": {
"sanctum": {
"command": "sanctum",
"args": ["mcp", "serve"]
}
}
}
| Tool | Config Location |
|---|---|
| Claude Desktop | claude_desktop_config.json |
| Claude Code | .claude/mcp.json |
| Cursor | .cursor/mcp.json |
| Windsurf | .windsurf/mcp.json |
| VS Code (Copilot) | .vscode/mcp.json |
The MCP server authenticates to the vault as its own agent with its own Ed25519 identity and policy-scoped access. Your AI assistant never sees raw credentials — the vault decides what to allow.
Architecture
Everything runs locally on your machine. No cloud, no external services, no internet needed.
AI Editors (Claude Code, Cursor, Windsurf, ...)
| MCP Protocol (stdio)
v
MCP Server (sanctum mcp serve)
| JSON-RPC (Unix socket)
v
Vault Daemon (localhost only)
|-- Policy Engine (IAM-style, deny-by-default)
|-- Transparent Proxy (credential injection, SSRF protection)
|-- Session Manager (Ed25519 auth, lease tracking)
|-- Audit Logger (HMAC hash-chained)
+-- Encrypted Store (SQLite + AES-256-GCM envelope encryption)
|
~/.sanctum/
|-- vault.db (encrypted, 0600)
|-- keys/ (encrypted, 0700)
+-- audit.log (hash-chained)
Key Commands
| Command | What It Does |
|---|---|
sanctum init | Create encrypted vault and generate keys |
sanctum store <name> | Store a credential (prompts securely) |
sanctum agent register <name> | Create an agent with Ed25519 identity |
sanctum policy add <name> | Create an access policy |
sanctum policy simulate | Dry-run policy evaluation |
sanctum daemon start | Start vault daemon + web dashboard |
sanctum mcp serve | Start MCP server for AI editors |
sanctum lock | Seal the vault immediately |
sanctum unlock | Unseal the vault |
sanctum audit verify | Verify audit log integrity |
sanctum scan | Scan for exposed credentials |
sanctum demo | Run full end-to-end demo |
SDKs
The SDKs are CRP v2 clients: the primary call is use(service), which
returns a short-lived use-handle — a proxyBaseUrl and a session token — not
a secret. You point your existing HTTP SDK's base URL at the handle and the
vault injects the real credential in transit. The secret never enters your
agent's process, so the SDKs uphold "use, don't retrieve" by design.
| Language | Package | Install |
|---|---|---|
| Python | sanctum-sdk (PyPI) | pip install sanctum-sdk |
| TypeScript / Node | @sanctumai/sdk (npm) | npm install @sanctumai/sdk |
| Rust | sanctum-sdk (crates.io) | cargo add sanctum-sdk |
| Go | module | go get github.com/SanctumSec/sanctum/sdk/go |
from sanctum_sdk import VaultClient
from openai import OpenAI
with VaultClient("my-agent") as sanctum:
h = sanctum.use("openai") # → a handle, never the secret
client = OpenAI(base_url=h.proxy_base_url, # point the SDK at the vault
api_key=h.token) # your session, not the key
client.chat.completions.create(...) # vault injects the real key
Source for all four lives under sdk/. retrieve() (raw secret) remains
available as deny-by-default break-glass, but the handle flow above is primary.
Security
Design Principles
- Memory-safe Rust. The only
unsafeislibc::mlock/munlock/setrlimitinsanctum-core(keep keys out of swap; disable core dumps). Secrets usesecrecy::Secret<T>+Zeroize. - Encrypted at rest. AES-256-GCM envelope encryption + SQLCipher AES-256 for the metadata DB (which refuses to run as plain SQLite). Unique DEK per credential. Master key derived via Scrypt (N=2^18, ~256 MiB/guess).
- Deny by default. No policy = no access. Policies are additive only.
- Tamper-evident audit. HMAC SHA-256 hash chain logs every access attempt — allows and denials.
sanctum audit verifydetects tampering. - Credential leasing. Time-bounded access with automatic expiry. Credentials are zeroized from memory when leases end.
- Loopback-only. Daemon binds to
127.0.0.1only. Never exposed to the network. - Real recovery. A recovery phrase (enrolled at init) wraps the master key as an independent unlock path — lose your passphrase, recover with the phrase and reset it. Not a placebo: the phrase is verified end-to-end.
- Runtime hardening. Core dumps disabled at startup (no key spill on a crash), a 12-character minimum passphrase, and an optional idle auto-lock (
SANCTUM_AUTO_LOCK_MINUTES) that re-seals the vault after inactivity.
Protection Layers
| Layer | Mechanism |
|---|---|
| At rest | AES-256-GCM envelope + SQLCipher AES-256 DB, per-credential DEK, Scrypt-derived master key |
| Key storage | macOS Keychain (hardware-backed) / encrypted file fallback |
| Recovery | Recovery phrase wraps the master key — independent unlock + passphrase reset |
| In memory | secrecy + zeroize, TTL auto-expiry, core dumps disabled |
| Idle | Optional auto-lock re-seals the vault after inactivity |
| Authentication | Ed25519 challenge-response, 12-char minimum passphrase |
| Authorization | Policy engine with glob matching + escalating unlock rate-limit |
| Proxy | SSRF prevention, credential never leaves vault process |
| Audit | HMAC hash chain, cryptographic tamper detection |
Contributing
Contributions are welcome.
git clone https://github.com/SanctumSec/sanctum
cd sanctum
cargo build
cargo test
cargo clippy --workspace --all-targets -- -D warnings
Please open an issue before starting major work so we can coordinate.
Areas of Interest
- Streaming (SSE) model-response inspection
- New protocol analyzers — Bedrock/Vertex request shapes, vector DBs, RAG-context signals
- Tool-call gating (which functions an agent may invoke)
- Linux keyring integration — Secret Service API
- Fuzzing and property-based testing
- Security auditing
License
The SanctumAI core binary is licensed under the Business Source License 1.1.
What this means:
- ✅ Free for personal use, internal business use, and development
- ✅ Source available — inspect, audit, and contribute
- ✅ Security research — audit the crypto, policy engine, and audit chain
- ❌ Cannot be offered as a competing managed/hosted service without a commercial license
- 🔄 Converts to Apache 2.0 on March 8, 2030
For commercial licensing: jason@sanctumai.dev