mirage-proxy 0.4.0

Invisible sensitive data filter for LLM APIs — secrets, credentials, and PII replaced with plausible fakes
mirage-proxy-0.4.0 is not a library.

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

cargo install mirage-proxy

Pre-built binaries: see Releases.

Quick start

Auto-setup (recommended)

mirage-proxy --setup

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
mirage-proxy --target https://api.anthropic.com

# 2. Point your tool at mirage (localhost:8686)
export ANTHROPIC_BASE_URL=http://localhost:8686

Per-tool examples

Claude Code:

mirage-proxy --target https://api.anthropic.com
# Auto-configured by --setup, or manually:
# ~/.claude/settings.json → { "env": { "ANTHROPIC_BASE_URL": "http://localhost:8686" } }

Codex / OpenAI:

mirage-proxy --target https://api.openai.com
export OPENAI_BASE_URL=http://localhost:8686

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 debug for 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
Email 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
mirage-proxy --target https://api.anthropic.com --vault-key "my-passphrase"

# Via environment variable
MIRAGE_VAULT_KEY="my-passphrase" mirage-proxy --target https://api.anthropic.com

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:

mirage-proxy --target https://api.anthropic.com --dry-run

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:

{
  "timestamp": "2026-02-20T02:00:17Z",
  "kind": "EMAIL",
  "action": "masked",
  "confidence": 1.0,
  "value_hash": "d8a94b5c...",
  "context_snippet": "can you email chan@..."
}
  • 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_hash to 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

git clone https://github.com/chandika/mirage-proxy
cd mirage-proxy
cargo build --release
# Binary at target/release/mirage-proxy

Requires Rust 1.75+. No other dependencies.

Detection sources

Pattern detection draws from two open-source databases:

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.