All posts 🤖

6 Ways AI Agents Leak Your API Keys and Secrets

We watched Claude Code include a Stripe secret key in a debug log. It was trying to help — we had asked it to figure out why a payment integration was failing, and it printed the full HTTP request, headers and all. Authorization: Bearer sk_live_..., right there in the conversation context. Stored on Anthropic's servers, in the conversation history, visible in the terminal scrollback.

That is when we rebuilt how secrets reach an agent in the first place.

AI coding assistants are the most productive tools we have ever used. They are also the biggest credentials risk most developers are not thinking about. Here are six ways your secrets leak through AI agents — with reproduction steps, severity ratings, and fixes for each one.

6
leak vectors identified
5
MCP tools, zero CLI
0
raw values in the conversation
Leak #1 — Reading your .env file

1. AI agents read your .env file Critical

Every AI coding assistant with file access can read your .env file. It is a plaintext file in your project directory. The agent reads project files to understand context. There is no access control, no authentication, no prompt asking "should this tool see your Stripe key?"

How to reproduce it

Create a .env file with a test secret. Open any AI coding assistant. Ask it to "explain the project structure" or "help me debug the API integration." Watch the agent's tool calls — it will read the .env file as part of understanding your codebase.

# Create a test .env
echo 'TEST_SECRET=this-value-should-not-appear-in-chat' > .env

# In Claude Code:
> "What API services does this project use?"
# Agent reads .env → TEST_SECRET value is now in context

# In Cursor (0.45+):
# Open the project → Cmd+L → "Summarize the project config"
# Cursor indexes .env as part of the workspace

# In GitHub Copilot:
# Open .env in a tab → Copilot Chat has access to open file contents

The agent is not being malicious. It is doing what you asked: understand the codebase. Your .env is part of the codebase.

The fix: Do not have a .env file. Store secrets in the macOS Keychain instead. Agents load them at runtime by calling the bundled noxkey_get MCP tool, which returns a source '/tmp/...' command pointing at an encrypted, self-deleting handoff script. There is no file for the agent to read.

Leak #2 — Debug output exposure

2. Secrets in debug output Critical

"The API call is failing, can you debug this?" The agent helpfully prints the full request to show you what is happening:

# You ask Claude Code:
> "The Stripe charge endpoint is returning 401, can you debug?"

# Agent output:
Here's the failing request:

  curl -X POST https://api.stripe.com/v1/charges \
    -H "Authorization: Bearer sk_live_51ABC...xYz" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "amount=2000¤cy=usd"

The issue is that your API key has been rotated. The key
starting with sk_live_51ABC is no longer valid...

That key is now in your conversation history. On the AI provider's servers. In your terminal scrollback. If you are sharing your screen or recording a demo, it is in the recording too.

The agent saw the value in process.env.STRIPE_SECRET_KEY (referenced in your code) and included it because showing the full request seemed helpful for debugging. Right call for debugging. Wrong call for security.

The fix: Stop the raw value from reaching the agent in the first place. When agents request secrets through NoxKey's bundled MCP server (noxkey_get), the tool response carries only a source '/tmp/...' command pointing at an encrypted, self-deleting handoff script. The secret loads into the shell environment for subprocesses to use; the agent's text context never holds the literal value, so there is nothing for it to echo into a debug trace in the first place.

// Agent calls: noxkey_get(account: "myorg/project/STRIPE_KEY")
// Returns: source '/tmp/noxkey-mcp-xyz/secrets.sh'
// Agent runs that line in Bash; $STRIPE_KEY loads into the shell.
// Subsequent curl/HTTP calls use $STRIPE_KEY — the literal sk_live_51ABC...
// never appears in the agent's text context.

3. Credentials stored in conversation logs High

"Here is my config, help me fix this deployment." You paste your .env into ChatGPT. Here is where that data goes:

  1. Transmitted over HTTPS to the API endpoint
  2. Stored in the conversation database
  3. Logged for abuse detection and safety monitoring
  4. Potentially queued for human review if it triggers filters
  5. Retained per the data retention policy (which can change)
  6. Backed up across the provider's infrastructure

Six copies minimum, on infrastructure you do not control, with retention policies you did not agree to read. Even if you delete the conversation in the UI, the data was transmitted and stored. Deletion from the frontend does not guarantee deletion from logs, backups, or training pipelines.

How to reproduce it

# In ChatGPT, Claude, or any AI chat:
> "Here's my .env file, can you help me debug?"
> DATABASE_URL=postgresql://admin:s3cretP@[email protected]/prod
> STRIPE_KEY=sk_live_...

# That data is now stored on the provider's servers.
# You cannot un-send it. You cannot verify deletion.
# If you used a wrapper app or browser extension, add another copy.

The fix: Never paste credentials into any chat interface. If the agent needs access to a secret, it should flow through the OS — Keychain to encrypted handoff to process environment — not through the conversation.

4. API keys hardcoded in generated code High

You ask the agent to "set up the Stripe integration." It generates a config file. Because it saw your API key in the environment (or in a file it read earlier), it hardcodes the value:

# You ask Cursor:
> "Set up Stripe with the charge endpoint"

# Cursor generates config/stripe.ts:
export const stripeConfig = {
  secretKey: "sk_live_51ABC...xYz",  // <-- your actual key
  publishableKey: "pk_live_...",
  webhookSecret: "whsec_..."
};

# You review quickly, see the structure looks right, commit.
# Your production Stripe key is now in git history. Forever.

How to reproduce it

Set a STRIPE_SECRET_KEY environment variable in your shell. Open an AI coding assistant. Ask it to create an API integration file. In roughly 1 out of 3 attempts (in our testing with Cursor 0.45 and Claude Code), the agent uses the actual value instead of a process.env reference.

The fix: Use git-secrets or gitleaks as a pre-commit hook. But more fundamentally — if the agent never sees the raw secret value, it cannot hardcode it. When secrets flow through encrypted handoff, the agent has access to process.env.STRIPE_KEY but never the literal string. It generates process.env.STRIPE_KEY in code because that is all it knows.

5. Secrets inherited by spawned processes High

AI agents that can execute code spawn subprocesses. Environment variables are inherited by child processes by default:

Your shell
STRIPE_KEY=sk_live_...
inherits
claude
inherits STRIPE_KEY
inherits
node
inherits STRIPE_KEY
inherits
bash -c "curl ..."
inherits STRIPE_KEY
inherits
curl
has full access to STRIPE_KEY

Every process in that tree has your Stripe key. The agent did not steal it — it inherited it, the same way every child process inherits environment variables on Unix. When the agent spawns a curl command to test your API, that process can access $STRIPE_KEY.

How to reproduce it

# Set a secret in your shell
export TEST_SECRET="this-is-sensitive"

# In Claude Code:
> "Run: echo $TEST_SECRET"
# Output: this-is-sensitive

# The agent accessed an inherited environment variable
# and printed it to the conversation context.

Most agents do not actively exfiltrate credentials this way. But the capability is there. An agent with code execution and inherited environment variables has everything it needs to make authenticated API calls on your behalf.

The fix: Agents calling NoxKey's MCP server get a per-secret encrypted handoff for each noxkey_get rather than a fully populated environment they can hand down to children. For non-MCP shell callers (a build script, a manual source), process-tree detection still identifies the agent in the parent chain and tightens access automatically. Either way, the agent gets scoped, single-use access instead of full environment inheritance.

Leak #6 — The vector nobody is talking about

6. Credentials exposed through MCP tool-use High

MCP (Model Context Protocol) tools and function-calling plugins let agents make HTTP requests, query databases, and interact with external services. When those tools need authentication, the credentials often flow through the agent's context.

# MCP server config (e.g., in .cursor/mcp.json or claude_desktop_config.json):
{
  "mcpServers": {
    "database": {
      "command": "npx",
      "args": ["@modelcontextprotocol/server-postgres",
               "postgresql://admin:s3cretP@[email protected]/prod"]
    }
  }
}

That connection string — with the password — sits in a JSON config file. The agent reads it. The MCP server process inherits it. If the agent logs the connection for debugging, the password is in the conversation.

It gets worse with HTTP-based tools. An agent calling a REST API through an MCP tool might construct the request with an Authorization header. The tool call and its parameters — including the auth header — are part of the conversation context. Logged, stored, and visible in the conversation history.

How to reproduce it

# Set up an MCP server with credentials in the config
# Ask the agent to "query the database for recent users"
# Watch the tool call — the connection string (including password)
# appears in the agent's tool invocation log

# Or: configure an API tool with an auth header
# Ask the agent to "fetch my account details from the API"
# The Authorization header appears in the tool call parameters

The fix: Never put credentials in MCP config files as plaintext. Use environment variable references that resolve at runtime. Better yet, have the MCP server pull credentials from the Keychain directly, so the agent never sees or transmits the auth values.

The root cause behind every AI agent secret leak

All six leaks share one root cause: secrets and agents occupy the same space. The secret is in a file the agent reads, an environment it inherits, a config it parses, or a conversation it participates in.

Secrets and agents occupy the same space. The fix is separation.

The solution is separation. Secrets flow through secure channels — Keychain to encrypted handoff to process environment. Agents operate in their context — text, conversation, code generation. The two never mix.

Five MCP tools, zero CLI

Encrypted handoff: keep raw values out of the conversation

NoxKey's bundled MCP server exposes five tools — noxkey_show, noxkey_get, noxkey_set, plus advanced noxkey_scan and noxkey_admin. Every retrieval flows through encrypted handoff: the tool response carries a source '/tmp/...' command, not the raw value.

// Agent calls: noxkey_get(account: "myorg/project/STRIPE_KEY")
// Returns:    source '/tmp/noxkey-mcp-xyz/secrets.sh'
// Agent runs the source line in Bash — $STRIPE_KEY loads into the shell.
// The handoff script is single-use and self-deletes the moment it's sourced.
//
// Subsequent curl/HTTP calls in the same shell use $STRIPE_KEY transparently.
// The literal sk_live_51ABC... never appears in the agent's text context.

This addresses leaks 2, 3, and 4 at the source — debug output, conversation logs, and generated code can't include credential values the agent never saw. It does not prevent the agent from reading existing .env files (leak 1) or inheriting environment variables that other tools loaded (leak 5), which is why eliminating .env files and routing every fetch through the MCP server are both necessary.

Per-secret security_level (easy, normal, strict, off_limits) controls how often Touch ID is required and which secrets agents can see at all. Strict secrets re-prompt on every access; off_limits secrets are never released to agents. Set the policy when you store the key.

How to secure your API keys from AI agents right now

The leaks are real. The fixes exist. Here is the priority order:

  1. Delete your .env files. Move secrets to the Keychain. This eliminates leak #1 entirely. (Here is how we did it.)
  2. Route every fetch through MCP. Agents call noxkey_get instead of reading credential files. The encrypted handoff response prevents leaks #2, #3, and #4 — the raw value never reaches the agent's text context.
  3. Use process-tree detection for shell callers. Build scripts and manual source calls that bypass MCP still get tightened automatically when an agent is in the parent chain. Prevents leak #5.
  4. Audit your MCP configs. Remove hardcoded credentials from tool server configurations.
  5. Stop pasting credentials into chats. There is no technical fix for voluntarily sending secrets to a third party.

Install NoxKey from the Mac App Store. The bundled MCP server auto-registers with Claude Code, Cursor, Windsurf, and Claude Desktop on first launch — no JSON to write, no tokens to paste.

Key Takeaway

AI agents leak secrets through six vectors: reading .env files, debug output, conversation logs, generated code, process inheritance, and MCP tool-use. All six share the same root cause — secrets and agents occupy the same space. The fix is separation: store secrets in the Keychain, route every fetch through NoxKey's bundled MCP server, and let encrypted handoff keep raw values out of the conversation entirely. Touch ID ensures no agent can access a secret without your physical confirmation.