Credential Vault
Tank Vault is a format-preserving tokenization proxy that sits between your AI agent and the LLM provider. It intercepts outgoing requests, replaces real credentials with structurally identical fakes, and restores them in responses — so the model never sees your actual API keys, database URLs, or tokens.
Why This Matters
AI agents routinely pass environment variables, config files, and code context to LLM providers. If that context contains API keys, database URLs, or tokens:
- The model provider sees your production secrets in training-eligible API logs
- A prompt injection in a skill could instruct the model to exfiltrate credentials
- Stolen tokens from LLM provider breaches expose your infrastructure
Tank Vault eliminates this entire attack class. Real credentials never leave your machine.
How It Works
1. Credential Detection
The scanner uses 10 built-in patterns to detect credentials in outgoing request bodies:
| Pattern ID | Detects | Prefix |
|---|---|---|
stripe_secret | Stripe Secret Keys | sk_live_ / sk_test_ |
stripe_publishable | Stripe Publishable Keys | pk_live_ / pk_test_ |
aws_access_key | AWS Access Key IDs | AKIA |
github_pat | GitHub Personal Access Tokens | ghp_ |
github_oauth | GitHub OAuth Tokens | gho_ |
openai_key | OpenAI API Keys | sk-proj- / sk- |
elevenlabs_key | ElevenLabs API Keys | elvn_ |
jwt_token | JWT Tokens | eyJ |
database_url | Database Connection Strings | postgresql:// / mysql:// / mongodb:// |
slack_webhook | Slack Webhook URLs | https://hooks.slack.com/services/ |
2. Format-Preserving Tokenization
When a credential is detected, Vault generates a structurally identical fake — same prefix, same length, same character set. The fake passes format validation in the model's context but is cryptographically unrelated to the real credential.
Real: sk_live_<REAL_SECRET>
Fake: sk_live_<FAKE_SECRET> ← same prefix, same shape, clearly non-production placeholders
Security properties:
- Fakes are generated using
crypto.getRandomValues()(CSPRNG) - No 5+ character overlap between real and fake suffixes (brute-force resistant)
- Each real credential maps to exactly one fake (bidirectional mapping)
- Mapping is held in-memory only — never written to disk
3. Proxy Interception
The proxy only redacts requests that look like AI generation calls — specifically, POST requests with a JSON body containing a messages array (the standard chat completion format). All other traffic passes through unmodified.
On the response path, the proxy restores all fake tokens back to their real values, so the agent receives correct credentials in model output.
Architecture
The vault package has four layers:
| Layer | Files | Responsibility |
|---|---|---|
| Detector | detector/patterns.ts, detector/scanner.ts | Pattern-match credentials in text using 10 regex rules |
| Tokenizer | tokenizer/generator.ts, tokenizer/vault.ts | Generate fakes, maintain bidirectional real↔fake mapping |
| Proxy | proxy/server.ts, proxy/interceptor.ts, proxy/streaming.ts | HTTP proxy that redacts outgoing AI requests and restores responses |
| Runner | runner/agents.ts, runner/run.ts, proxy/bootstrap.cjs | Launch agents with env overrides to route traffic through the proxy |
Supported Agents
The runner knows how to proxy traffic for these AI agents:
| Agent | Runtime | Proxy Strategy |
|---|---|---|
| Claude (Claude Code) | Node.js | base-url-overrides — rewrites ANTHROPIC_BASE_URL, OPENAI_BASE_URL, etc. |
| OpenCode | Bun | base-url-overrides — same env var rewriting |
| Cursor | Electron | https-proxy — sets HTTPS_PROXY / HTTP_PROXY env vars |
| Codex | Rust | https-proxy — sets HTTPS_PROXY / HTTP_PROXY env vars |
| OpenClaw | Unknown | best-effort — combines all strategies |
| Universal | Unknown | best-effort — combines all strategies |
Proxy Strategies
| Strategy | How It Works |
|---|---|
base-url-overrides | Rewrites ANTHROPIC_BASE_URL, OPENAI_BASE_URL, MISTRAL_BASE_URL, GROQ_BASE_URL to point at the vault proxy. The agent thinks it's talking to the real API. |
https-proxy | Sets standard HTTPS_PROXY and HTTP_PROXY environment variables. Works with any HTTP client that respects proxy settings. |
node-options | Injects --require bootstrap.cjs via NODE_OPTIONS, which patches globalThis.fetch to route all traffic through the proxy. Also sets HTTPS_PROXY. |
best-effort | Applies all three strategies simultaneously. Used when the agent's runtime is unknown. |
Target URL Routing
The proxy supports two methods for determining where to forward requests:
Header-based (used by bootstrap.cjs)
POST /proxy HTTP/1.1
x-target-url: https://api.anthropic.com/v1/messages
Content-Type: application/json
{ "messages": [...] }
Path-based (used by base-url-overrides)
The original API base URL is base64url-encoded into the proxy path:
Original: https://api.anthropic.com
Proxy URL: http://127.0.0.1:{port}/_/aHR0cHM6Ly9hcGkuYW50aHJvcGljLmNvbQ/v1/messages
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
base64url-encoded original base URL
The proxy decodes the base URL and appends the remaining path to reconstruct the full target URL.
Programmatic API
Scan for credentials
import { scan } from "@tankpkg/vault";
const matches = scan("Connect to postgresql://admin:[email protected]:5432/app");
// [{ start: 11, end: 57, patternId: 'database_url' }]
Generate format-preserving fakes
import { generateFake } from "@tankpkg/vault";
const fake = generateFake("sk_live_<REAL_SECRET>", "stripe_secret");
// 'sk_live_<FAKE_SECRET>' — same prefix, same shape, random placeholder token
Use the vault store for bidirectional mapping
import { VaultStore } from "@tankpkg/vault";
const vault = new VaultStore();
// Store a real → fake mapping
vault.store("sk_live_real123", "sk_live_fake456", "stripe_secret");
// Redact all known credentials in a text block
const redacted = vault.redact("My key is sk_live_real123");
// 'My key is sk_live_fake456'
// Restore originals from redacted text
const restored = vault.restore(redacted);
// 'My key is sk_live_real123'
Access credential patterns
import { CREDENTIAL_PATTERNS } from "@tankpkg/vault";
for (const pattern of CREDENTIAL_PATTERNS) {
console.log(pattern.id, pattern.prefix, pattern.label);
}
Security Model
What Vault protects against
| Threat | How Vault prevents it |
|---|---|
| Credential leakage to LLM providers | Real keys never appear in API request bodies — only format-preserving fakes |
| Prompt injection exfiltration | Even if a skill tricks the model into outputting credentials, the output contains fakes |
| LLM provider data breaches | Logs at the provider side contain only fake credentials |
| Training data contamination | Your secrets can't appear in future model training data |
What Vault does NOT protect against
| Limitation | Why |
|---|---|
| Credentials in non-AI traffic | Only POST requests with messages arrays are redacted |
| Runtime secret access | An agent process can still read process.env directly — Vault protects the LLM channel, not the local process |
| Streaming responses | streaming.ts is currently a passthrough — full streaming support is planned |
Cryptographic properties
- CSPRNG: Fake suffixes generated via
crypto.getRandomValues()with rejection sampling to avoid modulo bias - No overlap guarantee: Generator retries up to 10 times to ensure no 5+ character substring overlap between real and fake suffixes
- In-memory only: The real↔fake mapping lives in
VaultStore(a pair ofMapobjects) — never serialized to disk, never logged
Package Details
@tankpkg/vault v0.1.0
├── src/detector/ — credential pattern matching
├── src/tokenizer/ — fake generation + bidirectional vault store
├── src/proxy/ — HTTP proxy server + AI request interceptor
└── src/runner/ — agent launch configs + environment wiring
Zero runtime dependencies. Built with tsdown, tested with Vitest.
Further Reading
- Security Model — How Tank's 6-stage security pipeline scans skills
- Permissions — Declare and enforce what skills can access
- CLI Reference — All
tankcommands includingtank vault(planned) - Self-Hosting — Run your own registry with full security infrastructure