# Phantom — Full Reference Phantom is an open-source CLI that protects API keys from AI coding agent leaks. It replaces real secrets in .env files with worthless "phantom tokens" (phm_...) and injects real credentials at the network layer via a local HTTP reverse proxy. Website: https://phm.dev GitHub: https://github.com/ashlrai/phantom-secrets License: MIT Language: Rust Install: `npx phantom-secrets init` or `brew tap ashlrai/phantom && brew install phantom` MCP: `claude mcp add phantom-secrets-mcp -- npx phantom-secrets-mcp` ## The Problem AI coding tools read your .env file. Real API keys enter the LLM context window. They can leak via session logs and transcripts, prompt injection attacks, malicious MCP servers, code generation (AI writes keys into files), and potential training data exposure. GitGuardian reports 39.6M secrets leaked on GitHub in 2025. AI-assisted commits leak at 2x the baseline rate. ## How Phantom Works 1. `phantom init` reads your .env, stores real secrets in an encrypted vault (OS keychain or ChaCha20-Poly1305 encrypted file), and rewrites .env with worthless phantom tokens (phm_...). 2. `phantom exec -- claude` starts a local HTTP reverse proxy on 127.0.0.1. It sets BASE_URL environment variables (e.g., OPENAI_BASE_URL=http://127.0.0.1:PORT/openai) so API calls route through the proxy, then launches the specified command. 3. When code makes an API call, the proxy intercepts it, replaces phantom tokens with real credentials in request headers and body, and forwards over TLS to the real API endpoint. Full streaming/SSE support for OpenAI and Anthropic. 4. Session-scoped tokens: each `phantom exec` session generates fresh phantom tokens. If tokens leak from AI logs, they are already invalid. 5. The AI agent never sees, logs, or transmits a real secret. ## Install ### npx (recommended) ``` npx phantom-secrets init ``` Downloads the correct platform binary automatically. No Rust toolchain needed. ### Homebrew (macOS) ``` brew tap ashlrai/phantom brew install phantom ``` ### Direct binary download Download from: https://github.com/ashlrai/phantom-secrets/releases ### Cargo (from source) ``` cargo install phantom --git https://github.com/ashlrai/phantom-secrets ``` ### Verify ``` phantom --version ``` ## CLI Commands (27 total) ### phantom init Read .env, store real secrets in vault, rewrite with phantom tokens, create .phantom.toml. Options: --from (non-standard .env location) Safe to re-run: new secrets are added, existing tokens preserved. ### phantom exec -- Start local proxy, set *_BASE_URL env vars, run command, shut down proxy on exit. Example: phantom exec -- claude Example: phantom exec -- cursor . Example: phantom exec -- node server.js ### phantom start / phantom stop Standalone proxy lifecycle. --daemon flag runs in background. phantom start --daemon phantom stop ### phantom list Show secret names stored in vault. Never shows values. ### phantom add Add a secret to the vault. Writes phantom token to .env. Example: phantom add STRIPE_SECRET_KEY sk_live_abc123... ### phantom remove Remove a secret from the vault permanently. ### phantom reveal View a real secret value. Blocked in non-interactive contexts by default. Options: --clipboard (auto-clears after 30s), --yes (bypass interactive check) ### phantom rotate Regenerate all phantom tokens in .env. Real secrets in vault are unchanged. Options: --sync (push updated tokens to deployment platforms simultaneously) ### phantom status Show project ID, vault backend, secret count, service mappings, proxy state. ### phantom doctor Health check: config validity, vault access, .env protection, .gitignore, .env.example, pre-commit hook. Options: --fix (auto-fix safe issues) ### phantom check Pre-commit hook. Scans .env files for unprotected real secrets. Exit code 1 if found. Example: phantom check --staged ### phantom sync Push real secrets to deployment platforms. Example: phantom sync --platform vercel --project prj_abc123 Example: phantom sync --platform railway --project ### phantom pull Pull secrets from deployment platforms into local vault. Example: phantom pull --from vercel --project prj_abc123 Example: phantom pull --from railway --project --environment production Options: --force (overwrite existing local secrets) ### phantom env Generate .env.example with secret values masked and non-secrets preserved. Options: --output ### phantom setup Configure Claude Code MCP server and permissions automatically. Writes to .claude/settings.local.json. ### phantom login Sign in to Phantom Cloud via GitHub OAuth (device code flow). ### phantom logout Sign out of Phantom Cloud. ### phantom cloud push Encrypt vault client-side and upload to Phantom Cloud. Server never sees plaintext. ### phantom cloud pull Download and decrypt vault from Phantom Cloud. Options: --force (overwrite existing local secrets) ### phantom cloud status Check cloud auth status, plan, and last sync version. ### phantom export Export encrypted backup file independent of cloud sync. Example: phantom export --passphrase mypassword Creates: phantom-export.enc ### phantom import Restore secrets from encrypted backup. Example: phantom import phantom-export.enc --passphrase mypassword ### phantom wrap Wrap package.json scripts with `npx phantom-secrets exec --` so secrets are injected at runtime. Saves originals as script:raw variants. Options: --only (whitelist), --skip (blacklist) ### phantom unwrap Reverse phantom wrap. Restores original scripts from :raw variants. ### phantom watch Watch .env for changes and update vault automatically. ### phantom why Explain why an environment variable is or isn't protected (shows classification heuristic). ### phantom copy --to Copy a secret to another phantom-initialized project without exposing the value. Options: --rename ### phantom team list / create / members / invite Team management commands. [TBD: requires Pro tier] ## MCP Server — 17 Tools Install: `claude mcp add phantom-secrets-mcp -- npx phantom-secrets-mcp` The MCP server uses stdio transport via the rmcp 1.3 SDK. It works with Claude Code, Cursor, Windsurf, and OpenAI Codex. ### Read-only tools (safe to call any time) phantom_list_secrets List secret names stored in vault. Never returns values. Returns: count and names, with service mappings if configured. phantom_status Show project ID, vault backend, secret count, service mappings, .env state. Returns: formatted status report. phantom_cloud_status Check Phantom Cloud auth status, plan tier, and last sync version. Returns: login status, plan, vault count if logged in. phantom_why Explain why a specific .env key is or isn't classified as a secret. Params: key (string) Returns: classification (PROTECTED/UNPROTECTED/PUBLIC KEY/NOT SECRET) with heuristic explanation. phantom_check Scan for security issues. Params: runtime (bool) — if true, scans process environment for phantom tokens without proxy Returns: list of unprotected secrets or phantom token warnings. phantom_sync Show sync configuration and what would be pushed to deployment platforms. Params: platform (string, optional), project_id (string, optional) Returns: sync targets, secrets to be synced. Does NOT execute sync. phantom_doctor Run all health checks. Params: fix (bool) — auto-fix safe issues if true Returns: pass/warn/fail for each check, summary. Checks: .phantom.toml, vault access, .env protection, .gitignore, .env.example, pre-commit hook. ### Initialization tools phantom_init Read .env, store real secrets in vault, rewrite with phantom tokens, write .phantom.toml. Params: env_path (string, default: ".env") Returns: list of protected secrets. phantom_env Generate .env.example from .env. Secrets replaced with descriptive placeholders. Params: output (string, default: ".env.example") Returns: confirmation with entry count. ### Destructive tools — require confirm=true All destructive tools return an error if called without confirm=true. The agent must ask the user for explicit consent before calling any of these. phantom_add_secret Add a new secret to the vault. Overwrites if name already exists. Params: name (string), value (string), confirm (bool, must be true) Effect: stores secret, writes phantom token to .env. phantom_remove_secret Permanently delete a secret from the vault. Params: name (string), confirm (bool, must be true) Effect: secret is gone — not recoverable unless cloud backup exists. phantom_rotate Regenerate all phantom tokens in .env. Old tokens immediately invalid. Params: confirm (bool, must be true) Effect: .env rewritten with new phm_ tokens. Real secrets unchanged. phantom_cloud_push Encrypt vault client-side and upload to Phantom Cloud. Params: confirm (bool, must be true) Effect: overwrites existing cloud copy. Requires phantom login first. Encryption: ChaCha20-Poly1305 client-side. Server never sees plaintext. phantom_cloud_pull Download and decrypt vault from Phantom Cloud. Params: force (bool), confirm (bool, must be true) Effect: writes secrets into local vault. force=true overwrites existing entries. phantom_copy_secret Copy a secret from this vault to another phantom-initialized project. Params: name (string), target_dir (string), rename (string, optional), confirm (bool, must be true) Effect: writes into target vault. .. path traversal is rejected. phantom_wrap Wrap package.json scripts with `npx phantom-secrets exec --`. Params: only (array), skip (array), confirm (bool, must be true) Effect: modifies package.json. Saves originals as script:raw variants. phantom_unwrap Restore original package.json scripts from :raw variants. Params: confirm (bool, must be true) Effect: restores and removes :raw entries. ### MCP setup by IDE Claude Code: claude mcp add phantom-secrets-mcp -- npx phantom-secrets-mcp Cursor: Settings > Features > MCP Servers Name: phantom, Command: npx, Args: phantom-secrets-mcp Windsurf: {"mcpServers": {"phantom": {"command": "npx", "args": ["phantom-secrets-mcp"]}}} OpenAI Codex (~/.codex/config.json): {"mcpServers": {"phantom": {"command": "npx", "args": ["phantom-secrets-mcp"]}}} ## Architecture 5-crate Rust workspace: phantom-core Config (.phantom.toml), .env parsing/rewriting, phantom token generation (256-bit CSPRNG, phm_ prefix), error types, auth, cloud API client, smart secret detection heuristics. phantom-vault VaultBackend trait with OS keychain (macOS Keychain, Linux Secret Service) and encrypted file fallback. Shared crypto module: ChaCha20-Poly1305 with Argon2id key derivation (32-byte salt, 256-bit key). phantom-proxy HTTP reverse proxy on 127.0.0.1. Receives plaintext HTTP, replaces phantom tokens in headers/body with real secrets from vault, forwards over TLS. Uses hyper for server, reqwest for outbound HTTPS. Full streaming/SSE support for OpenAI and Anthropic APIs. phantom-cli clap-based CLI binary. 27 commands. Uses colored for output, anyhow for error handling. CLI output uses -> (info), ok (success), ! (error), warn (warning) prefixes. phantom-mcp MCP server using rmcp 1.3 SDK. Stdio transport. 24 tools. apps/web Next.js backend on Vercel. Supabase for database. Stripe for billing. Device auth API, vault sync API, billing webhooks. ## Security Model Real secrets never on disk in project directory — only in OS keychain or encrypted vault. Vault encryption: ChaCha20-Poly1305 with Argon2id key derivation (32-byte salt, 256-bit key). Phantom tokens: 256-bit CSPRNG with phm_ prefix. Never collide with real API key formats (sk-*, ghp_*, AKIA*, etc.). Proxy binds to 127.0.0.1 only — never exposed to network. Proxy token authentication prevents other local processes from using the proxy. Secrets zeroized from memory after use (zeroize crate). Allowlist model: proxy only injects for configured service URL patterns. Unknown services are not modified. Session-scoped tokens: fresh per exec session. Tokens from previous sessions are immediately invalid. Cloud tokens stored hashed (SHA-256) in database — never plaintext. MCP destructive tools require explicit confirm=true — prompt injection cannot silently modify vaults. phantom_reveal blocked in non-interactive contexts by default to prevent AI agents from reading real values. ## Smart Secret Detection Phantom automatically identifies secrets vs config values using heuristics. Key patterns that match as secrets: *_KEY, *_SECRET*, *_TOKEN, *_PASSWORD, *_PASSWD, *_CREDENTIAL, *_AUTH, *_PRIVATE *_API_KEY, *_ACCESS_KEY, *_SIGNING DATABASE_URL, REDIS_URL, MONGO_URL, POSTGRES_URL, MYSQL_URL, AMQP_URL RABBITMQ_URL, ELASTICSEARCH_URL, CONNECTION_STRING, DSN Value patterns that match as secrets: sk-*, sk_*, ghp_*, github_pat_*, glpat-*, xoxb-*, AKIA*, Bearer, eyJ Connection strings with @ in URLs High-entropy strings (32+ chars of hex/base64) Public key prefixes (never protected — browser-safe by convention): NEXT_PUBLIC_*, EXPO_PUBLIC_*, VITE_*, REACT_APP_*, NUXT_PUBLIC_*, GATSBY_* Non-secrets left untouched: NODE_ENV, PORT, DEBUG, APP_NAME, LOG_LEVEL, and similar config values ## Configuration (.phantom.toml) ```toml [phantom] project_id = "a1b2c3d4-..." # unique per project directory # Custom service proxy mapping [services.my_api] secret_key = "MY_API_KEY" pattern = "api.example.com" # URL pattern to match header = "X-Api-Key" header_format = "{secret}" # Deployment sync targets [[sync]] platform = "vercel" token_env = "VERCEL_TOKEN" project_id = "prj_abc123" targets = ["production", "preview"] [[sync]] platform = "railway" token_env = "RAILWAY_TOKEN" project_id = "your-railway-project-id" service_id = "optional-service-id" environment_id = "optional-env-id" # Cloud sync versioning (written automatically) [cloud] version = 7 ``` ## Platform Sync Push secrets to deployment platforms: ``` phantom sync --platform vercel --project prj_xxx phantom sync --platform railway --project xxx ``` Pull secrets from platforms (new machine onboarding): ``` phantom pull --from vercel --project prj_xxx phantom pull --from railway --project xxx --environment production phantom pull --from vercel --project prj_xxx --force # overwrite existing ``` Supported platforms: Vercel, Railway. Requires platform API token in environment. ## Cloud Sync Architecture Phantom Cloud provides end-to-end encrypted vault sync. Zero-knowledge: server stores opaque encrypted blobs. Never sees plaintext secrets. Client-side encryption: ChaCha20-Poly1305 with Argon2id key derivation. Encryption key stored only in user's OS keychain. Optimistic concurrency: version numbers prevent conflicting pushes from multiple devices. Auth: GitHub OAuth via device code flow (like GitHub CLI). Pricing: Free tier: 1 cloud vault, unlimited local vaults Pro ($8/mo): unlimited cloud vaults, multi-device sync ## CI/CD In environments without an OS keychain (Docker, GitHub Actions, CI runners), Phantom falls back to an encrypted file vault at ~/.phantom/vaults/. Set the passphrase via environment variable: ``` export PHANTOM_VAULT_PASSPHRASE="your-secure-passphrase" ``` GitHub Actions example: ```yaml - name: Install Phantom run: cargo install phantom --git https://github.com/ashlrai/phantom-secrets - name: Pull secrets run: phantom pull --from vercel --project ${{ vars.VERCEL_PROJECT_ID }} env: PHANTOM_VAULT_PASSPHRASE: ${{ secrets.PHANTOM_VAULT_PASSPHRASE }} VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} - name: Run tests run: phantom exec -- npm test env: PHANTOM_VAULT_PASSPHRASE: ${{ secrets.PHANTOM_VAULT_PASSPHRASE }} ``` ## Monorepo Support Running `phantom init` in a subdirectory with its own .env creates a project-scoped vault. Each subdirectory can have its own .phantom.toml and independent secret set. A single `phantom exec` session handles all of them. ## What to Commit Commit: .phantom.toml — proxy config and service definitions, no secrets .env.example — generated by `phantom env`, shows required variable names Do not commit: .env — contains phantom tokens specific to your vault .env.local — same reason .env*.local — same reason ## Common Use Cases 1. Solo developer with Claude Code: phantom init, then phantom exec -- claude 2. Delegate to Claude: add MCP server, say "protect my API keys" — Claude runs phantom_init 3. Multi-device sync: phantom login, phantom cloud push on one machine, phantom cloud pull on another 4. Deploy to Vercel: phantom sync --platform vercel --project prj_xxx 5. New machine onboarding: phantom pull --from vercel --project prj_xxx 6. Vault backup: phantom export --passphrase mypass / phantom import backup.enc --passphrase mypass 7. Team onboarding: phantom env generates .env.example with placeholders 8. CI/CD: set PHANTOM_VAULT_PASSPHRASE, use phantom in GitHub Actions 9. Pre-commit safety: phantom check in git hooks or pre-commit framework 10. Rotate with sync: phantom rotate --sync regenerates tokens and pushes to deployment platforms 11. Monorepo: phantom init in each subdirectory — each gets its own vault and config 12. Multi-IDE team: same MCP server works across Claude Code, Cursor, Windsurf, and Codex ## Test and Quality Stats Tests: 69 (at last count) Clippy: zero warnings (enforced in CI with -D warnings) License: MIT Rust edition: 2021