We asked Claude Code to debug a failing Stripe integration. It printed the full HTTP request — headers included. Our live API key, right there in the conversation log. Stored on Anthropic's servers. Visible in terminal scrollback. Gone.
The agent wasn't being malicious. It was doing exactly what we asked. Our .env file was just sitting there in the project directory, and to Claude Code, it's just another file.
We've since documented six specific ways this happens. But documenting the problem wasn't enough. We needed to actually fix it. Here are five strategies that work — ranked from simplest to most paranoid.
First, understand the blast radius
Claude Code, Cursor, Copilot, Codex — they all have file system access. They need it to be useful. They read your source code, your config, your project structure. And your .env file sits right there alongside everything else.
Once an agent reads a secret, it can end up in:
- Conversation logs — stored on the provider's servers, subject to their retention policies
- Debug output — printed to your terminal, visible in screen recordings
- Generated code — hardcoded into files that get committed to git
- Context windows — where other tools in the same session can see it
None of this is the agent's fault. Your credentials are in the blast radius because you put them in a plaintext file with zero access control.
1. Get secrets off disk
The simplest fix is the best one. No file, no leak.
.env files are plaintext — no encryption, no auth, readable by any process running as your user. Move your secrets to your OS credential store instead. On macOS, that's the Keychain: hardware-encrypted, Touch ID protected, and — crucially — not a file sitting in your project directory.
# The old way: plaintext file any process can read
cat .env
STRIPE_KEY=sk_live_51Hx...
# The new way: import to Keychain, delete the file
# Drag the .env onto the NoxKey menu bar icon — or
# from an agent, scan then import under one Touch ID:
# noxkey_scan(path: ".", suggested_org: "myorg",
# suggested_project: "general")
# noxkey_admin(action: "import", entries: [...])
rm .env
# Load when you need it (agent calls the MCP tool):
# noxkey_get(account: "myorg/general/STRIPE_KEY")
# → returns: source '/tmp/noxkey_xxx.sh'
# Run that line in Bash → $STRIPE_KEY in env, no file on disk
This alone eliminates the most common leak vector. Everything else is defense in depth.
2. Inject at runtime, not from files
The dotenv pattern reads a file at startup. That file exists on disk for your entire development session. Runtime injection is different — secrets flow from the credential store directly into your process environment, on demand.
# dotenv: secret lives on disk, readable by anything
require('dotenv').config()
# Runtime injection via MCP: secret lives in memory only
# noxkey_get(account: "myorg/general/STRIPE_KEY")
# → run the returned source line in Bash
node app.js
# process.env.STRIPE_KEY works, but no file exists
When you need multiple secrets, asking for a prefix cuts the friction:
# One Touch ID, every key under the prefix loaded into your shell
# noxkey_get(account: "myorg/general")
# → run the returned source line in Bash
# $STRIPE_KEY, $DATABASE_URL, $OPENAI_API_KEY are now in the environment
# Subsequent get calls under myorg are cached for the session window
The secret exists only in your shell's memory. When the session ends, it's gone.
3. Detect the agent, change the delivery
Here's a question most credential managers don't ask: is a human requesting this secret, or is an AI agent?
It matters. When you click "Copy" in the menu bar app, you want the value on your clipboard. When Claude Code calls noxkey_get through MCP, the value should never enter the conversation.
NoxKey treats the agent as a first-class caller. The bundled MCP server is the path agents use — noxkey_get always returns a self-deleting source command, never a raw value. For shell processes that aren't going through MCP, process-tree detection picks up the slack: every process has a parent, walk up the tree, and you can see who's really asking.
Terminal → zsh → claude → bash -c → noxkey
↑
agent detected here
Either way, when NoxKey is talking to an agent it switches behavior automatically:
- Encrypts the secret (ChaCha20-Poly1305, one-time key)
- Writes it to a self-deleting temp script (chmod 700, 60-second TTL)
- Returns
source /tmp/...— the agent runs this, secret loads into env - The raw value never appears in the conversation
The agent can use the secret — it's in process.env. It just never sees it.
4. Make leaked keys expire fast
Long-lived API keys are risky with or without AI agents. But agents make it worse. If a leaked key expires in 15 minutes, the damage window is tiny. If it's been valid for two years, the damage window is the rest of its life.
Where your services support it, prefer short-lived credentials:
- OAuth with refresh flows — access tokens expire, refresh tokens stay in the Keychain
- AWS STS — scoped to a role, expires after 1-12 hours
- Scoped API keys — restrict to only the permissions your dev workflow needs
For keys that can't expire, establish a rotation habit. One update in your credential store beats finding-and-replacing across six .env files scattered across your machine.
All four together
Each strategy covers a different leak vector:
| Leak vector | Strategy |
|---|---|
| Agent reads .env file | Get secrets off disk |
| Secret persists on disk | Runtime injection |
| Agent accesses credential store | Process-tree detection |
| Leaked key stays valid | Short-lived tokens |
We built NoxKey because we needed all four in one tool. We were using Claude Code every day and kept finding our own API keys in places they shouldn't be. Three steps and it's done:
# 1. Install NoxKey from the Mac App Store
# 2. Drag your .env onto the menu bar icon → review sheet
# → Touch ID once → all keys imported
# 3. Delete the .env, flip Agent Guard on in Settings
rm .env
Frequently asked questions
- Can Claude Code actually read my .env files?
- Yes. Claude Code has full file system access and reads project files to understand context. Your
.envis just another file in the project directory. There's no access control preventing it. The fix is to not have a .env file. - Does Cursor index my API keys?
- Cursor indexes your workspace for context-aware suggestions. If your workspace contains a
.envfile, those values are part of the index. Moving secrets to the Keychain means there's nothing to index. - How does process-tree detection actually work?
- When a process requests a secret, NoxKey walks the process hierarchy upward — child to parent to grandparent — looking for known AI agent processes. If one is found, the secret is delivered via encrypted handoff instead of as a raw value. Full technical deep-dive here.
- Can the AI agent still use my API key for testing?
- Yes. The agent calls the
noxkey_getMCP tool, runs the returnedsourceline in Bash, and the secret loads intoprocess.envvia the encrypted handoff. The agent can run tests, make API calls, and debug integrations — it just can't see the raw value. It writesprocess.env.STRIPE_KEYin code because that's the only interface it knows. - Is this macOS only?
- NoxKey is macOS only (built on the Keychain and Secure Enclave). But the strategies are universal — on Linux use the system keyring or
pass, on Windows use the Credential Manager. The principle is the same: secrets off disk, into the OS credential store.
Free. No account. No cloud. Your secrets stay on your machine.