Skip to content
AI/LLM: This documentation page is available in plain markdown format at /docs/vault.md

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.

Without Vault Agent sends sk_live_<REAL…> Provider logs your real key Breach 💀 → Attacker has your Stripe account With Vault Agent sends sk_live_<FAKE…> Provider logs only the fake Breach 🤷 → Your Stripe account is safe How Vault Swaps Real: sk_live_<REAL_SECRET> Fake: sk_live_<FAKE_SECRET> Same prefix: sk_live_ Same length: 32 chars The fake has the same prefix + length — the model doesn't notice the difference

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 IDDetectsPrefix
stripe_secretStripe Secret Keyssk_live_ / sk_test_
stripe_publishableStripe Publishable Keyspk_live_ / pk_test_
aws_access_keyAWS Access Key IDsAKIA
github_patGitHub Personal Access Tokensghp_
github_oauthGitHub OAuth Tokensgho_
openai_keyOpenAI API Keyssk-proj- / sk-
elevenlabs_keyElevenLabs API Keyselvn_
jwt_tokenJWT TokenseyJ
database_urlDatabase Connection Stringspostgresql:// / mysql:// / mongodb://
slack_webhookSlack Webhook URLshttps://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

Vault Internal Architecture Detector patterns.ts — 10 regexes scanner.ts — scan(text) sorted, deduped matches Tokenizer generator.ts — CSPRNG fakes vault.ts — bidirectional map redact() / restore() Proxy Server server.ts — HTTP proxy interceptor.ts — AI filter redact out → restore in Runner agents.ts — 6 agent configs (Claude, OpenCode, Cursor, Codex…) run.ts — spawn agent with env overrides strategies: base-url-overrides | https-proxy | node-options | best-effort spawns bootstrap.cjs — patches globalThis.fetch to route through proxy (Node.js --require)

The vault package has four layers:

LayerFilesResponsibility
Detectordetector/patterns.ts, detector/scanner.tsPattern-match credentials in text using 10 regex rules
Tokenizertokenizer/generator.ts, tokenizer/vault.tsGenerate fakes, maintain bidirectional real↔fake mapping
Proxyproxy/server.ts, proxy/interceptor.ts, proxy/streaming.tsHTTP proxy that redacts outgoing AI requests and restores responses
Runnerrunner/agents.ts, runner/run.ts, proxy/bootstrap.cjsLaunch agents with env overrides to route traffic through the proxy

Supported Agents

Agent detected runtime check Node.js? Bun? yes base-url-overrides Electron? Rust? yes https-proxy unknown best-effort (all strategies)

The runner knows how to proxy traffic for these AI agents:

AgentRuntimeProxy Strategy
Claude (Claude Code)Node.jsbase-url-overrides — rewrites ANTHROPIC_BASE_URL, OPENAI_BASE_URL, etc.
OpenCodeBunbase-url-overrides — same env var rewriting
CursorElectronhttps-proxy — sets HTTPS_PROXY / HTTP_PROXY env vars
CodexRusthttps-proxy — sets HTTPS_PROXY / HTTP_PROXY env vars
OpenClawUnknownbest-effort — combines all strategies
UniversalUnknownbest-effort — combines all strategies

Proxy Strategies

StrategyHow It Works
base-url-overridesRewrites 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-proxySets standard HTTPS_PROXY and HTTP_PROXY environment variables. Works with any HTTP client that respects proxy settings.
node-optionsInjects --require bootstrap.cjs via NODE_OPTIONS, which patches globalThis.fetch to route all traffic through the proxy. Also sets HTTPS_PROXY.
best-effortApplies 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

ThreatHow Vault prevents it
Credential leakage to LLM providersReal keys never appear in API request bodies — only format-preserving fakes
Prompt injection exfiltrationEven if a skill tricks the model into outputting credentials, the output contains fakes
LLM provider data breachesLogs at the provider side contain only fake credentials
Training data contaminationYour secrets can't appear in future model training data

What Vault does NOT protect against

LimitationWhy
Credentials in non-AI trafficOnly POST requests with messages arrays are redacted
Runtime secret accessAn agent process can still read process.env directly — Vault protects the LLM channel, not the local process
Streaming responsesstreaming.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 of Map objects) — 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 tank commands including tank vault (planned)
  • Self-Hosting — Run your own registry with full security infrastructure

Command Palette

Search skills, docs, and navigate Tank