Odel
SanctumAI — Credential Vault for AI Agents

SanctumAI — Credential Vault for AI Agents

Local
@sanctumsecRustUpdated 1mo ago

Credential vault for AI agents — encrypted storage, lease-based access, CRP support.

SanctumAI

The data-access firewall for AI agents.

CI Release License

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:

  1. 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.
  2. 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 .env files, shell configs, and project directories.
  • Lock/Unlocksanctum lock seals the vault instantly. sanctum unlock opens it.
  • Policy Simulationsanctum policy simulate for dry-run policy evaluation before deploying rules.
  • Demo Modesanctum demo shows 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: use keeps 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_header to 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.

Live agent activity feed — a SQL export and a secret-carrying prompt denied inline, with the matched rule shown on each event

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 builder: operation ~ model.prompt AND NOT model ~ grok-* → Deny, dry-run against the last 6 real signals shows exactly which requests it would block

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:

  1. Validates the agent's lease, identity, and policy permissions
  2. Decrypts the credential (or brokers a short-lived token, e.g. Entra Agent ID)
  3. Injects it into the request (bearer token, API key header, HMAC signature)
  4. Forwards the HTTP call upstream
  5. Returns the upstream response to the agent
  6. The credential never leaves the vault process

Supported Injection Methods

MethodExample
Bearer tokenAuthorization: Bearer sk-...
API key headerX-API-Key: ...
Custom headerAny header name you configure
HMAC signingRequest signature with credential as signing key

SSRF Prevention

The proxy blocks requests to internal networks:

  • 127.0.0.1, localhost, ::1
  • 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
  • 169.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"]
    }
  }
}
ToolConfig Location
Claude Desktopclaude_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

CommandWhat It Does
sanctum initCreate 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 simulateDry-run policy evaluation
sanctum daemon startStart vault daemon + web dashboard
sanctum mcp serveStart MCP server for AI editors
sanctum lockSeal the vault immediately
sanctum unlockUnseal the vault
sanctum audit verifyVerify audit log integrity
sanctum scanScan for exposed credentials
sanctum demoRun 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.

LanguagePackageInstall
Pythonsanctum-sdk (PyPI)pip install sanctum-sdk
TypeScript / Node@sanctumai/sdk (npm)npm install @sanctumai/sdk
Rustsanctum-sdk (crates.io)cargo add sanctum-sdk
Gomodulego 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

  1. Memory-safe Rust. The only unsafe is libc::mlock/munlock/setrlimit in sanctum-core (keep keys out of swap; disable core dumps). Secrets use secrecy::Secret<T> + Zeroize.
  2. 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).
  3. Deny by default. No policy = no access. Policies are additive only.
  4. Tamper-evident audit. HMAC SHA-256 hash chain logs every access attempt — allows and denials. sanctum audit verify detects tampering.
  5. Credential leasing. Time-bounded access with automatic expiry. Credentials are zeroized from memory when leases end.
  6. Loopback-only. Daemon binds to 127.0.0.1 only. Never exposed to the network.
  7. 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.
  8. 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

LayerMechanism
At restAES-256-GCM envelope + SQLCipher AES-256 DB, per-credential DEK, Scrypt-derived master key
Key storagemacOS Keychain (hardware-backed) / encrypted file fallback
RecoveryRecovery phrase wraps the master key — independent unlock + passphrase reset
In memorysecrecy + zeroize, TTL auto-expiry, core dumps disabled
IdleOptional auto-lock re-seals the vault after inactivity
AuthenticationEd25519 challenge-response, 12-char minimum passphrase
AuthorizationPolicy engine with glob matching + escalating unlock rate-limit
ProxySSRF prevention, credential never leaves vault process
AuditHMAC 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