npx phantom-secrets init # installs Phantom and protects your .env
phantom agent doctor # verify the repo is safe for AI agents
phantom exec -- claude # run Claude Code with real secrets injected by proxy
That's it. Your AI tool never sees a real key again.
Phantom replaces real API keys in your .env with random 256-bit tokens (phm_...) and stores the real values in your OS keychain. When you run phantom exec -- <cmd>, a local HTTP reverse proxy starts on 127.0.0.1, service SDKs are redirected through *_BASE_URL environment variables, and the proxy session is authenticated with a fresh PHANTOM_PROXY_TOKEN. CLI-generated SDK URLs include the token as a local /_phantom/<token>/ path segment so unmodified SDKs work; header-aware clients can set PHANTOM_PROXY_HEADER_AUTH_ONLY=1 and send x-phantom-proxy-token instead. The proxy removes its local auth token before forwarding, swaps phantom tokens for real credentials in request headers and body, then forwards over TLS to the actual API endpoint. The AI agent reads .env, gets only worthless tokens, and its logs and context windows contain nothing sensitive.
For text/* and application/x-www-form-urlencoded request bodies the proxy replaces tokens frame-by-frame without buffering the full payload (streaming token replacement). JSON bodies use a buffered path with field-level scoping to avoid substituting tokens that appear in non-secret fields such as prompt or messages. Full SSE/streaming responses (OpenAI, Anthropic) are preserved end-to-end.
For a detailed breakdown of assets protected, threat actors, mitigations, and known gaps, see THREAT_MODEL.md.
npx phantom-secrets init
Downloads the correct platform binary and runs phantom init in one step.
npm install -g phantom-secrets
phantom init
brew tap ashlrai/phantom
brew install phantom
Download from GitHub Releases, extract, and place phantom on your $PATH.
phantom --version
# phantom 0.6.0
$ cd my-project
$ phantom init
-> Scanning .env...
-> Found 3 secret(s): OPENAI_API_KEY, ANTHROPIC_API_KEY, DATABASE_URL
-> Storing secrets in OS keychain...
ok OPENAI_API_KEY stored
ok ANTHROPIC_API_KEY stored
ok DATABASE_URL stored
-> Rewriting .env with phantom tokens...
ok .env updated
-> Writing .phantom.toml...
ok Initialized. Run `phantom exec -- <cmd>` to start the proxy.
Your .env now contains:
# Managed by Phantom -- do not edit phantom tokens manually
OPENAI_API_KEY=phm_a7f3b9e2c4d1f8a3b6e9d2c5f8a1b4e7
ANTHROPIC_API_KEY=phm_d4e7a0b3c6f9e2d5a8c1b4f7e0d3a6c9
DATABASE_URL=phm_b1c4d7e0a3f6b9c2d5e8a1b4c7d0e3f6
NODE_ENV=development
PORT=3000
Non-secret values (NODE_ENV, PORT) are left untouched. If your .env is at a non-standard path:
phantom init --from .env.local
Reads .env, stores real secrets in the OS keychain, rewrites .env with phantom tokens, creates .phantom.toml. Safe to re-run: new secrets are added, existing phantom tokens are preserved.
phantom init
phantom init --from .env.local
Multi-project — protect every repo in a workspace at once:
phantom init --all ~/code --dry-run # preview which repos would be touched
phantom init --all ~/code # run init in every git repo with a .env
phantom init --all ~/code --jobs 8 # run up to 8 repos concurrently (default: 4)
--all walks the directory, finds every git repo with one of .env, .env.local, .env.development, .env.production, etc., and runs init in each. Skips repos that already have .phantom.toml, plus node_modules, target, dist, build, and dot-dirs. A progress bar shows live status. The parallelism default can also be set via the PHANTOM_INIT_JOBS environment variable.
# Positional value (backward-compatible, but ends up in shell history):
phantom add STRIPE_SECRET_KEY sk_live_abc123...
# Interactive prompt — value is read silently from the terminal (no echo):
phantom add STRIPE_SECRET_KEY
# Pipe the value from a secret manager or CI helper (no tty required):
echo "$SECRET_VALUE" | phantom add STRIPE_SECRET_KEY --stdin
op read "op://Prod/Stripe/key" | phantom add STRIPE_SECRET_KEY --stdin
phantom remove STRIPE_SECRET_KEY
add stores the value and writes a phantom token to .env. When no positional value is given, phantom prompts silently on the terminal so the secret never enters your shell history. Use --stdin for non-interactive / CI use. remove deletes from the vault (.env token line is left; remove manually if desired).
Regenerates all phantom tokens without changing the real secrets. Use this if you suspect a token mapping was exposed (tokens are worthless without the proxy, but rotation is a clean reset).
phantom rotate
Sync your vault across machines. End-to-end encrypted — the server never sees plaintext.
phantom login # GitHub OAuth, once per device
phantom cloud push # upload encrypted vault
phantom cloud pull # download and decrypt on another machine
Push real secrets to a deployment platform, or pull them from one.
# Push to Vercel
phantom sync --platform vercel --project prj_abc123
# Push only Stripe secrets (glob pattern — avoids dev-only keys going to prod)
phantom sync --platform railway --project <id> --only "STRIPE_*"
# Multiple --only flags are OR-ed together
phantom sync --platform vercel --project prj_abc123 --only "STRIPE_*" --only "*_KEY"
# Preview without decrypting values or touching platform APIs
phantom sync --platform vercel --project prj_abc123 --dry-run --json
# Pull from Vercel on a new machine
phantom pull --from vercel --project prj_abc123
# Railway
phantom sync --platform railway --project <id>
phantom pull --from railway --project <id> --environment production
You can also hard-code filters per [[sync]] block in .phantom.toml so that phantom sync (no flags) always respects them:
[[sync]]
platform = "railway"
token_env = "RAILWAY_TOKEN"
project_id = "<id>"
only = ["STRIPE_*", "SENDGRID_*"] # never push DEV_* or DEBUG_* to prod
Scans .env files for unprotected secrets. Use as a pre-commit hook.
phantom check
# BLOCKED Unprotected secrets detected!
# ! .env: OPENAI_API_KEY is not protected
# fix Run `phantom init`
Exit code 0 = clean. Exit code 1 = unprotected secrets found.
Health check for your setup: config validity, vault access, .env protection, .gitignore coverage, .env.example, pre-commit hook.
phantom doctor
phantom doctor --fix # auto-fix safe issues
$ phantom doctor
pass: .phantom.toml found
pass: Config valid (project: a1b2c3d4)
pass: Vault backend: macOS Keychain
pass: 3 secret(s) in vault
pass: .env has 5 entries, all protected
pass: .env is in .gitignore
warn: No .env.example — team onboarding may be difficult
warn: No pre-commit hook installed
2 issue(s) found — use --fix to auto-fix
One command answers whether this repo is ready for autonomous AI coding tools.
phantom agent doctor # human-readable readiness view
phantom agent report --json # stable JSON for automation and CI
phantom agent setup --dry-run # preview safe setup changes
phantom agent setup --apply # initialize safe defaults
The readiness engine checks .env files, .phantom.toml, vault access, unprotected secrets, .env.example, .gitignore, MCP client wiring, package script wrapping, pre-commit protection, audit status, cloud login, team state, and sync targets. It returns one state: unsafe, protected, verified, team-ready, or compliance-ready.
Print a real secret value to stdout. Blocked in non-interactive contexts by default.
phantom reveal OPENAI_API_KEY
phantom reveal OPENAI_API_KEY --clipboard # copies and auto-clears after 30s
phantom reveal OPENAI_API_KEY --yes # bypass interactive check (scripts/CI)
One command per AI client — Phantom writes the right config file in the right place:
phantom setup --client claude # .claude/settings.local.json (project)
phantom setup --client cursor # ~/.cursor/mcp.json
phantom setup --client windsurf # ~/.codeium/windsurf/mcp_config.json
phantom setup --client codex # ~/.codex/config.toml
phantom setup --client claude --print # snippet to stdout for any other client
If phantom-mcp isn't on PATH, the writer falls back to npx -y phantom-secrets-mcp so the config still works on a fresh machine. For Claude Code specifically, phantom setup --client claude also allow-lists .env so Claude can read the (now-tokenized) file. See claude-code.md for the full workflow — the AI gains 25 Phantom MCP tools.
Restart the AI tool after running phantom setup so it picks up the new config.
Run phantom agent doctor after setup to confirm the repo is ready before handing work to Claude Code, Codex, Cursor, Windsurf, or another agent.
For compliance or forensics, set PHANTOM_AUDIT=1 to record every vault store/retrieve/delete to ~/.phantom/audit.log:
export PHANTOM_AUDIT=1
phantom exec -- npm run dev
Each line is a JSON object with ts, op, name (the secret name — never the value), process, and pid. Off by default; turn on per-shell or in your .envrc / .zprofile.
# Print the last 50 events (default)
phantom audit show
# Filter by operation or secret name
phantom audit show --op vault.store --name OPENAI_API_KEY
# Follow the log live (like tail -f)
phantom audit tail
# Print the absolute path to the log file
phantom audit path
# Verify HMAC-SHA256 chain integrity — exits 1 if tampering detected
phantom audit verify
Each log entry is chained with HMAC-SHA256 and a signed head checkpoint. phantom audit verify detects malformed lines, modified or inserted entries, sequence gaps, prefix deletion, and log tail/head mismatches. It cannot prove the whole log and checkpoint were both deleted without an external backup or checkpoint.
Migrate secrets from Doppler, Infisical, dotenvx, 1Password, or a plain .env file without exposing values in your shell:
# Doppler: export secrets as JSON first, then import
phantom import --from doppler --file dump.json
# Infisical: export as .env, then import
phantom import --from infisical --file export.env
# dotenvx: import a plaintext .env (not an encrypted .env.vault)
phantom import --from dotenvx --file .env
# 1Password: export as JSON, then import
phantom import --from 1password --file 1p-export.json
# Plain .env file (same heuristics as phantom init)
phantom import --from env --file .env
After importing, run phantom init to replace any remaining plaintext secrets in your .env with phantom tokens.
Use --force to overwrite existing vault entries without prompting.
You haven't initialized in this directory.
phantom init
The proxy is not running or the *_BASE_URL variables aren't set. Always run your tool via phantom exec:
phantom exec -- node server.js
phantom exec -- claude
Check status:
phantom status
phantom doctor
Click "Always Allow" for the phantom-secrets entry. This appears once per application.
Set a passphrase and Phantom falls back to an encrypted file vault:
export PHANTOM_VAULT_PASSPHRASE="$(openssl rand -hex 32)"
Store this passphrase as a CI secret. See docs/ci-cd.md for full GitHub Actions and Docker examples.
The binary ships from GitHub Releases. Check your internet connection, then:
# Fallback: install from source
cargo install phantom-secrets
Or download the binary directly from github.com/ashlrai/phantom-secrets/releases.
No. Phantom tokens are safe for AI to read. They're random strings that are meaningless without the proxy. After phantom init, you can explicitly allow .env in Claude Code's settings — phantom setup --client claude does this automatically (or use --client cursor|windsurf|codex for other AI tools).
Once you've run phantom login and phantom cloud push, you can see your projects, vault sizes, last sync, plan tier, and team membership at phm.dev/dashboard. It's read-only — every mutation still happens via the CLI — but it's useful for "wait, which projects do I have backed up?" moments.
$ phantom open
# Opens https://phm.dev/dashboard in your browser. Aliases:
# phantom open billing | team | docs | github | pricing
- Claude Code integration — MCP tools, workflow examples, what Claude can and cannot do
- CI/CD setup — GitHub Actions, Docker, encrypted file vault
- Troubleshooting — extended issue reference
- Threat model — assets, actors, mitigations, known gaps, cryptography summary
- GitHub repository — source, issues, releases
- phm.dev — pricing, cloud sync, team features