mirage-proxy
Invisible sensitive data filter for LLM APIs. Single Rust binary, sub-millisecond overhead.
Your coding agent reads your .env, your codebase, your credentials — and sends all of it to the cloud. Mirage sits between your client and the provider, silently replacing sensitive data with plausible fakes. The LLM never knows. Your secrets never leave.
You: "Deploy key is AKIAIOSFODNN7EXAMPLE, email ops@acme.com"
↓
Mirage: "Deploy key is AKIAHQ7RN2XK5M3B9Y1T, email jordan.walker3@proton.me"
↓
Provider: (sees only fake data, responds normally)
↓
Mirage: (swaps fakes back to originals in the response)
↓
You: "Done! I've drafted the deploy script for ops@acme.com"
No [REDACTED]. No [[PERSON_1]]. The provider sees a completely normal request with completely fake data. Responses are rehydrated transparently.
Why this matters
On Feb 14, 2026, a critical vulnerability (CVE-2026-21852) was disclosed where Claude Code could be tricked into exfiltrating API keys via prompt injection. The same week, a Reddit post hit 1.7K upvotes: "My agent stole my API keys."
Every LLM coding tool — Claude Code, Codex, Cursor, Aider, Continue — sends your full codebase to the cloud. If there's a secret in your repo, it's in someone's training data. Mirage fixes this at the network layer, no code changes required.
Install
Pre-built binaries: see Releases.
Quick start
Auto-setup (recommended)
Scans for installed LLM tools (Claude Code, Cursor, Codex, Aider) and configures them to route through Mirage automatically. Edits config files, sets environment variables, done.
To undo: mirage-proxy --uninstall
Manual setup
# 1. Start mirage, pointing at your provider
# 2. Point your tool at mirage (localhost:8686)
Per-tool examples
Claude Code:
# Auto-configured by --setup, or manually:
# ~/.claude/settings.json → { "env": { "ANTHROPIC_BASE_URL": "http://localhost:8686" } }
Codex / OpenAI:
Cursor / Continue / Aider / OpenCode:
Point the provider base URL to http://localhost:8686. Everything else works as before.
Live output
Mirage shows a clean live display — no log spam, just what matters:
mirage-proxy v0.3.0
─────────────────────────────────────
listen: http://127.0.0.1:8686
target: https://api.anthropic.com
mode: medium
audit: ./mirage-audit.jsonl
─────────────────────────────────────
📎 session: claude-sonnet-4-20250514
🛡️ EMAIL → ops@•••e.com
🛡️ AWS_KEY → AKIA•••MPLE
⚠️ SECRET (warn) → EtUC••• [128 chars]
📊 1h 2m 3s │ 42 reqs │ 3 masked │ 1 sessions │ ↑2.1MB ↓890KB
- New detections print once and scroll up
- Stats bar updates in-place at the bottom
- Each unique PII value is only counted once — conversation history resends don't inflate numbers
--log-level debugfor verbose per-request logging
What it detects
Secrets & credentials (always redacted)
| Type | Example | Detection |
|---|---|---|
| AWS Access Keys | AKIAIOSFODNN7EXAMPLE |
Prefix AKIA, ASIA, ABIA, ACCA |
| GitHub Tokens | ghp_xxxxxxxxxxxx |
Prefix ghp_, ghs_, gho_, ghu_, ghr_ |
| OpenAI API Keys | sk-proj-abc123... |
Prefix sk-proj-, sk-ant- |
| Google API Keys | AIzaSyA... |
Prefix AIza |
| GitLab Tokens | glpat-xxxx |
Prefix glpat- |
| Slack Tokens | xoxb-xxx, xoxp-xxx |
Prefix xoxb-, xoxp-, xoxs- |
| Stripe Keys | sk_live_xxx, pk_live_xxx |
Prefix sk_live_, sk_test_, rk_live_ |
| Bearer Tokens | Authorization: Bearer xxx |
Pattern match |
| PEM Private Keys | -----BEGIN RSA PRIVATE KEY----- |
Structural |
| Connection Strings | postgres://user:pass@host/db |
URI scheme + credentials |
| High-entropy strings | Unknown format secrets | Shannon entropy > threshold |
Personal data (masked with plausible fakes)
| Type | Original | Fake |
|---|---|---|
sam@acme.com |
jordan.walker3@proton.me |
|
| Phone (intl) | +1-555-123-4567 |
+1-237-153-1071 |
| Phone (US) | (555) 123-4567 |
(237) 153-1071 |
| SSN | 123-45-6789 |
537-28-4071 |
| Credit Card | 4111 1111 1111 1111 |
4532 7891 2345 6178 |
| IP Address | 10.0.1.42 |
172.18.3.97 |
How fakes work
Every fake matches the format and length of the original:
- An email becomes a different plausible email with a matching-length domain
- An AWS key becomes a different valid-format AWS key
- A phone number keeps its country code and formatting
- A credit card keeps its issuer prefix and passes Luhn validation
Session consistency: Within a conversation, sam@acme.com always maps to the same fake. The LLM's context stays coherent. Different conversations get different fakes.
Configuration
Works with zero config. For fine-tuning, create mirage.yaml:
target: "https://api.anthropic.com"
port: 8686
bind: "127.0.0.1"
sensitivity: medium # low | medium | high | paranoid
rules:
# Always redact — LLM never needs the real value
always_redact:
- SSN
- CREDIT_CARD
- PRIVATE_KEY
- AWS_KEY
- GITHUB_TOKEN
- API_KEY
- BEARER_TOKEN
# Replace with plausible fakes
mask:
- EMAIL
- PHONE
# Log but don't modify (too context-dependent)
warn_only:
- IP_ADDRESS
- CONNECTION_STRING
- SECRET
# Never redact these patterns
allowlist:
- "192.168.1.*"
- "sk-test-*"
- "localhost"
audit:
enabled: true
path: "./mirage-audit.jsonl"
log_values: false # true = log original values (for debugging only!)
dry_run: false
Sensitivity levels
| Level | What gets filtered |
|---|---|
low |
Only always_redact (secrets, keys, credentials) |
medium |
Secrets + PII masking (email, phone) — default |
high |
Everything including warn_only categories |
paranoid |
All detected PII regardless of category rules |
Sessions
Mirage groups requests into sessions by model name. Claude Code typically creates 1-2 sessions (e.g., claude-sonnet-4-20250514 + claude-haiku-3.5).
Within a session:
- Same PII → same fake (conversation stays coherent)
- Dedup: PII in conversation history isn't re-counted
- Audit log only records first occurrence
For explicit session control, add "mirage_session": "my-session-id" to request bodies.
Encrypted vault
Persist mappings across restarts so conversations stay consistent:
# With passphrase
# Via environment variable
MIRAGE_VAULT_KEY="my-passphrase"
The vault file (mirage-vault.enc) uses AES-256-GCM encryption. Without the passphrase, it's random bytes. Mappings are scoped per session.
Without --vault-key, mappings live in memory only and reset on restart.
Dry run
See what would be caught without modifying traffic:
Requests pass through unmodified. Detections are still logged to the audit file and shown in the live display. Use this to verify detection accuracy before going live.
Audit log
Every new detection is logged to mirage-audit.jsonl:
- Values are hashed (MD5) by default — original values never stored unless
audit.log_values: true - Only first occurrence per proxy lifetime is logged (no duplicates from conversation history)
- Use
value_hashto correlate detections across sessions
Streaming
Full SSE streaming support. Mirage handles text/event-stream responses from providers (Claude, OpenAI, etc.), rehydrating fakes in real-time as chunks arrive. No buffering delays.
CLI reference
mirage-proxy [OPTIONS]
Options:
-t, --target <URL> Target LLM API base URL
-p, --port <PORT> Listen port [default: 8686]
-b, --bind <ADDR> Bind address [default: 127.0.0.1]
-c, --config <PATH> Config file path
--sensitivity <LEVEL> low | medium | high | paranoid
--dry-run Log detections without modifying traffic
--vault-key <PASSPHRASE> Vault encryption passphrase (or MIRAGE_VAULT_KEY env)
--vault-path <PATH> Vault file path [default: ./mirage-vault.enc]
--vault-flush-threshold <N> Auto-flush after N new mappings [default: 50]
--setup Auto-configure installed LLM tools
--uninstall Remove mirage configuration from all tools
--log-level <LEVEL> trace | debug | info | warn | error [default: info]
-h, --help Print help
-V, --version Print version
How it compares
| mirage-proxy | PasteGuard | LLM Guard | LiteLLM+Presidio | |
|---|---|---|---|---|
| Install | cargo install |
Docker + npm | pip + models | pip + Docker + spaCy |
| Binary size | ~5MB | ~500MB+ | ~2GB+ | ~500MB+ |
| Overhead | <1ms | 10-50ms | 50-200ms | 10-50ms |
| Substitution | Plausible fakes | [[PERSON_1]] tokens |
[REDACTED] |
<PERSON> tokens |
| LLM knows? | No | Yes | Yes | Yes |
| Session-aware | Yes | No | No | No |
| Encrypted vault | Yes | No | No | No |
| Rehydration | Yes | Yes | No | Partial |
| Streaming | Yes | Yes | No | Partial |
| Dedup | Yes | No | No | No |
| Auto-setup | Yes | No | No | No |
The key difference: other tools use visible tokens that tell the LLM data was removed. The LLM adapts its behavior — it might refuse to write code involving [[PERSON_1]], or generate awkward workarounds. Mirage's fakes are invisible — the LLM processes the request normally because it looks normal.
Architecture
┌─────────────┐ ┌─────────────────────────────────────────┐ ┌──────────────┐
│ LLM Client │────▶│ mirage-proxy │────▶│ LLM Provider │
│ (Claude Code│ │ │ │ (Anthropic, │
│ Cursor, │◀────│ Request: detect PII → fake → forward │◀────│ OpenAI, │
│ Codex) │ │ Response: detect fakes → rehydrate │ │ etc.) │
└─────────────┘ │ │ └──────────────┘
│ ┌─────────┐ ┌────────┐ ┌───────────┐ │
│ │Redactor │ │ Faker │ │ Session │ │
│ │(detect) │ │(fakes) │ │ (mapping) │ │
│ └─────────┘ └────────┘ └───────────┘ │
│ ┌─────────┐ ┌────────┐ ┌───────────┐ │
│ │ Audit │ │ Vault │ │ Config │ │
│ │ (log) │ │(crypt) │ │ (rules) │ │
│ └─────────┘ └────────┘ └───────────┘ │
└─────────────────────────────────────────┘
Request path: Client → Mirage parses JSON → detects PII via regex + entropy → generates format-matching fakes → stores original↔fake mapping in session → forwards redacted request to provider.
Response path: Provider responds → Mirage scans for fake values → replaces fakes with originals (rehydration) → returns clean response to client. Works for both regular JSON responses and SSE streams.
Building from source
# Binary at target/release/mirage-proxy
Requires Rust 1.75+. No other dependencies.
Detection sources
Pattern detection draws from two open-source databases:
- Gitleaks (MIT) — prefix-based secret patterns
- secrets-patterns-db (Apache 2.0) — comprehensive pattern database
Only high-confidence, low-false-positive patterns are included. Generic "keyword near random string" patterns are excluded to avoid breaking legitimate code.
Roadmap
- Pattern + entropy detection (11 PII types)
- Invisible plausible fake substitution
- Session-scoped consistency with dedup
- Encrypted vault persistence (AES-256-GCM)
- SSE streaming rehydration
- Audit log + dry-run mode
- YAML config with sensitivity levels
- Auto-setup for Claude Code, Cursor, Codex, Aider
- Live TUI with in-place stats
- International phone number support
- Extended secret patterns (Gitleaks + secrets-patterns-db)
- Custom pattern definitions in config
- Allowlist/blocklist glob matching
- Optional ONNX NER for name/organization detection
- Route mode (sensitive requests → local model)
- Homebrew / npm / scoop distribution
- Pre-built binaries for macOS, Linux, Windows
License
MIT
Credits
Built by @chandika. Born from the frustration of watching coding agents send API keys to the cloud.
Pattern detection inspired by Gitleaks and secrets-patterns-db.