Skip to content
Integrations

Phantom Secrets

Stack is the control plane; Phantom is the vault. Understanding the split is the single most important mental model for operating Stack safely.

The split

Every secret the provider hands back — a Supabase service-role key, a Vercel token, an AWS secret access key — goes into Phantom, not into .stack.toml. Stack only records the slot name of each secret; Phantom holds the actual value, encrypted end-to-end.

Stack (control plane)
  • Drives provider OAuth / API flows
  • Creates upstream resources
  • Tracks which secrets each service needs (slot names)
  • Wires .env + .mcp.json
  • Never holds plaintext secrets
Phantom (vault)
  • Encrypts every secret at rest (AEAD)
  • Proxies phm_* tokens → real values at exec time
  • Syncs across machines via Phantom Cloud (optional)
  • Never exposes values to agents or editors
  • Has no opinion on what the keys mean

Why Stack refuses to run without Phantom

When you run stack add, stack import, or stack doctor, Stack calls phantom via its CLI. If Phantom isn't installed, there's nowhere to put the secrets — Stack would have to write them to disk in plaintext. We refuse, loudly:

$ stack add supabase
┌  stack add supabase

└  Phantom is not installed. Install it first:
     brew install ashlrai/phantom/phantom
   or
     npm i -g phantom-secrets

The one exception is stack init, which only writes the shape file — it warns but proceeds, so you can scaffold before installing Phantom.

The flow of a single secret

Take SUPABASE_SERVICE_ROLE_KEY as an example:

  1. You run stack add supabase.
  2. Stack opens Supabase OAuth in a browser; you click Authorise.
  3. Stack creates a new project via the Management API. The project is assigned a project ref (e.g. abcdefghijklmnop).
  4. Stack fetches the service_role key from Supabase. Exactly once, in memory.
  5. Stack calls phantom add SUPABASE_SERVICE_ROLE_KEY <value>. Phantom encrypts the value and writes it to the vault.
  6. Stack writes secrets = ["SUPABASE_SERVICE_ROLE_KEY"] into .stack.toml. The value never touches disk.
  7. Stack regenerates .env.local with Phantom tokens: SUPABASE_SERVICE_ROLE_KEY=phm_….
What's a phm_ token? It's a placeholder Phantom recognises. When your app runs under phantom exec (which is what stack exec wraps), Phantom swaps the token for the real secret at process-spawn time. The token is safe to check in; the real value never leaves the vault.

Runtime injection — stack exec

Your app reads secrets from process.env like normal. The trick is how they got there. Compare:

# bun dev straight up
# .env.local has SUPABASE_SERVICE_ROLE_KEY=phm_abc123
# → your app reads "phm_abc123" and breaks
bun dev

# stack exec wraps with Phantom's proxy
# → Phantom resolves phm_abc123 to the real value at spawn
# → your app reads the real key
stack exec -- bun dev

stack exec forwards everything after -- to phantom exec. Use it for dev servers, test runners, migrations — anything that needs real secrets locally.

Syncing the vault

Phantom Cloud (optional) gives you E2E-encrypted sync so the vault follows you across machines and into CI:

# set up cloud sync once
phantom login
phantom cloud push           # upload the current vault

# on another machine (or in CI)
phantom cloud pull --force   # download + decrypt the vault

stack ci init (see CI integration) writes a GitHub Actions workflow that runs phantom cloud pull before stack doctor --json.

Which Stack commands talk to Phantom

Command Needs Phantom? What it does with the vault
stack init No (warns) Only scaffolds .stack.toml.
stack add Yes Writes provisioned secrets into the vault.
stack import Yes Routes every key/value from a .env into the vault.
stack remove Yes Deletes the service's secrets from the vault.
stack env show / diff Yes Reads the vault's key list (never the values).
stack doctor Yes Reveals each secret briefly to verify it's still valid upstream.
stack exec Yes Forwards to phantom exec — Phantom does the proxy work.
stack list, stack info Yes (reveals presence only) Checks whether each declared slot is in the vault.
stack providers, stack templates, stack projects No Static catalog / registry data.

Installing Phantom

# macOS
brew install ashlrai/phantom/phantom

# anywhere Node runs
npm i -g phantom-secrets

# verify
phantom --version
phantom status

For the full Phantom story, see phantom.ashlr.ai.