← Back to Blog
· 7 min read · API Stronghold Team

The Phantom Token Pattern, but for Production AI Agents

Cover image for The Phantom Token Pattern, but for Production AI Agents

Your AI agent needs API keys to do anything useful. It calls OpenAI. It hits your internal APIs. It talks to third-party services. The standard answer to “how does it authenticate?” is: environment variables. Set OPENAI_API_KEY, let the SDK pick it up, done.

That answer works fine until someone asks a harder question: what happens when the agent itself is compromised?

The Environment Variable Problem

Prompt injection is not hypothetical. Researchers have demonstrated it repeatedly. Malicious content in a document, a webpage, or a tool response tricks the agent into running arbitrary instructions. The agent is already authenticated and already has your keys.

Run env | grep API_KEY and the agent hands over every credential in its environment. Post to an attacker-controlled endpoint, embed them in generated code, print them to a log file. The blast radius is whatever that API key can do.

On Linux, it’s worse. Any same-user process can read /proc/PID/environ. The credentials don’t even need to be exfiltrated through the agent’s own logic.

The conventional response to this is “use short-lived keys” or “rotate more often.” That helps. But rotating a key that just got stolen in the last 30 seconds doesn’t do much. The problem isn’t key age. It’s that the agent has the key at all.

What the Phantom Token Pattern Does

Luke Hinds (creator of Sigstore) introduced the phantom token pattern in nono, an open-source sandbox for AI coding agents. The core insight is simple: if the agent never sees the real credential, there’s nothing to steal.

Here’s the flow:

  1. At startup, the proxy generates a cryptographically random 256-bit session token. This is the “phantom token.”
  2. Real credentials load from a secure store outside the agent’s reach.
  3. The agent receives OPENAI_API_KEY=<64-char-hex-phantom-token> and OPENAI_BASE_URL=http://127.0.0.1:PORT/openai.
  4. The LLM SDK follows *_BASE_URL automatically, sending requests to the local proxy with the phantom token as its “API key.”
  5. The proxy validates the token with constant-time comparison, strips it, injects the real credential, and forwards the request upstream over TLS.

If the agent is compromised and exfiltrates its environment, the attacker gets a 64-character hex string that expires when the session ends and only works against a localhost port that’s no longer running. Useless.

nono’s implementation handles the details well: memory zeroization using Rust’s zeroize crate, DNS rebinding protection by resolving hostnames once and pinning connections to pre-resolved IPs, request size limits to prevent denial-of-service from malicious agent behavior. For a local dev sandbox, it’s solid.

Why Local Proxies Stop Working at the Team Boundary

nono is built for single-machine, single-user use. You run your agent locally, nono spins up a localhost proxy, the session ends when you close the terminal. That model works well for individual developers.

Production looks different.

Your agent runs in a CI/CD pipeline. Or in a cloud-hosted container. Or across a multi-tenant platform where different teams share infrastructure but shouldn’t share credentials. The localhost proxy assumption breaks immediately. There’s no single machine. There’s no single user. There’s no keystore to load from.

The other problem is operational: a DIY proxy has no audit trail, no usage analytics, no revocation surface. One commenter in the nono Reddit thread put the security critique plainly: you’ve rolled a custom proxy server from scratch, written by an LLM, never tested in the wild. HTTP desync, request smuggling, header reflection. Those aren’t theoretical attacks against HTTP proxies.

There’s also the multi-user credential problem. If six developers on a team all need to run agents against the same set of API keys, how do you manage access? Each person gets the raw key? That defeats the whole point. You need a vault backing the proxy, not just a local keystore.

How API Stronghold Implements It

api-stronghold-cli proxy start spins up a local reverse proxy, but the credentials don’t live on your machine. They live in the AS vault, encrypted with zero-knowledge encryption (PBKDF2 + AES-256-GCM). The CLI decrypts locally, holds the key in memory for the session, and the proxy injects it into outbound requests.

Session-scoped tokens work the same way as nono: 256-bit random tokens, constant-time validation, header stripping on inbound requests before real credential injection. The agent gets a phantom token. The real key never touches the agent’s environment.

What’s different is what backs the system:

Session lifecycle management. Sessions are created via POST /proxy/sessions and auto-expire (default 1 hour, max 24 hours). SIGINT revokes the session immediately. Keys are frozen at session creation, so permission changes after a session starts don’t silently affect running agents.

Group-based access control. Non-admin users only see keys from their assigned groups. Admins see everything. The proxy enforces this at session creation, not just at the UI layer.

Usage analytics. Every proxied request reports back: provider, model, HTTP status, duration. Batched and sent to the server. You get an audit trail of what your agent actually called, not just that it ran.

Provider coverage. The proxy routes requests for OpenAI, Anthropic, Google, Cohere, Mistral, Groq, Together, DeepSeek, and Perplexity. Startup prints the env var suggestions for all configured providers. No code changes needed in the agent; it follows *_BASE_URL like any other SDK.

The startup output looks like this:

Proxy listening on http://127.0.0.1:8900
Session expires: 2026-03-09T16:18:00Z

Set these environment variables:
  OPENAI_BASE_URL=http://127.0.0.1:8900/openai
  OPENAI_API_KEY=fake-key
  ANTHROPIC_BASE_URL=http://127.0.0.1:8900/anthropic
  ANTHROPIC_API_KEY=fake-key

The fake-key value gets stripped and replaced with the real credential before any request leaves the proxy.

Per-Call HMAC Signing

Session-scoped tokens are a good start, but every call in a session looks the same from an audit perspective. You know the session made 1,000 calls. You don’t know which call was the anomalous one.

Each proxied request in AS gets a UUID v4 request ID and an HMAC-SHA256 signature computed over a canonical string:

v1\n{requestId}\n{timestamp}\n{provider}\n{method}\n{path}

The session token is the HMAC key. The server verifies signatures on ingestion with constant-time comparison, rejects stale timestamps (more than 5 minutes in the future, more than 24 hours in the past), and stores the request ID and signed flag in analytics metadata.

The result: every individual API call is independently auditable. If you need to investigate what happened at 14:23:07 UTC, you have a signed record tied to the session that made it. If you need to revoke access to calls from a specific time window, you have the data to do it.

This is what the HN commenter stcredzero was describing independently: “key signing for specific API calls” where each request is signed with a timestamp and request ID, not just a session token. Tighter than phantom tokens alone. It’s shipped.

The Threat Model Difference

nono fills local dev sandboxes with kernel-level enforcement. That’s a strong security property for its context. Landlock on Linux, Seatbelt on macOS, network allowlists, filesystem restrictions. If you’re running untrusted agent code on your own machine, nono’s sandbox is doing real work.

AS fills the credential management layer for teams and production deployments. Multi-tenant vault, zero-knowledge encryption, group-based access, session lifecycle, per-call audit trail. The threat model is different: you’re not worried about your own machine’s process isolation. You’re worried about who has access to which keys across a team, what your agents called in the last 30 days, and whether you can cut off a compromised session in 30 seconds.

The patterns complement each other. An agent can run inside a nono sandbox for local filesystem and network isolation, and use AS for credential injection backed by a real vault. They’re solving adjacent problems.

Getting Started

Three commands:

# Install the CLI
brew install api-stronghold/tap/api-stronghold-cli

# Log in and add a key
api-stronghold-cli login
api-stronghold-cli keys add openai --value sk-proj-...

# Start the proxy
api-stronghold-cli proxy start

The proxy prints env var suggestions on startup. Set them in your agent’s environment, point your agent at the proxy, and run it. The agent sees a phantom token. The real key stays in the vault.

Sessions auto-expire. Revocation is immediate. Every call is signed and logged.

If you’re deploying agents in production and still passing raw API keys as environment variables, the phantom token pattern is the fix. The local proxy version works for personal dev. When you need audit trails, multi-user access control, and session revocation at the team level, you need something backing that proxy.

Sign up for API Stronghold or read the proxy documentation to see the full session lifecycle and signing spec.

Secure your API keys today

Stop storing credentials in Slack and .env files. API Stronghold provides enterprise-grade security with zero-knowledge encryption.

View Pricing →