All posts 🤖

6 Ways AI Agents Leak Your Secrets

I watched Claude Code include my Stripe secret key in a debug log. It was trying to help — I'd 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 my conversation history, visible in the terminal scrollback.

That's when I built the DLP guard.

AI coding assistants are the most productive tools I've ever used. They're also the biggest credentials risk most developers aren't 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
30s
to install the DLP guard
0
false positives in production
Leak #1 — Reading your .env file

1. Reading your .env file Critical

Every AI coding assistant with file access can read your .env file. It's a plaintext file in your project directory. The agent reads project files to understand context. There's 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 isn't being malicious. It's doing what you asked: understand the codebase. Your .env is part of the codebase.

The fix: Don't have a .env file. Store secrets in the macOS Keychain. Load them at runtime with eval "$(noxkey get org/project/KEY)". There's no file for the agent to read.

Leak #2 — Debug output exposure

2. Including 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's happening. Here's what that looks like in practice:

# 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're sharing your screen or recording a demo, it's 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. It made the right call from a debugging perspective and the wrong call from a security perspective.

The fix: A DLP guard that scans agent output before it enters the conversation. NoxKey's guard matches against 8-character fingerprints of your stored secrets:

# Set up the DLP guard (one-time)
noxkey guard install

# What it catches:
# Agent output: "Authorization: Bearer sk_live_51ABC..."
# Guard: "BLOCKED — output contains value matching myorg/project/STRIPE_KEY (peek: sk_live_5)"
# The output never enters the conversation context.

3. Storing credentials in conversation logs High

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

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

That's six copies minimum, on infrastructure you don't control, with retention policies you didn't agree to read. Even if you delete the conversation in the UI, the data was transmitted and stored. Deletion from the frontend doesn't 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. The secret should exist in the agent's runtime but never in its text context.

4. Committing secrets 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 to be helpful:

# 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 my testing with Cursor 0.45 and Claude Code), the agent will use 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 can't 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's all it knows.

5. Passing credentials to spawned processes High

AI agents that can execute code spawn subprocesses. Environment variables are inherited by child processes by default. Here's the inheritance chain:

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 didn't 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. If the agent constructs a request using that variable, it's making authenticated API calls as you.

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 don't actively exfiltrate credentials this way. But the capability is there. An agent with code execution and access to inherited environment variables has everything it needs to make authenticated API calls on your behalf.

The fix: Process-tree detection identifies when an AI agent — not a human — is requesting secrets. When an agent is detected, secrets are delivered via encrypted handoff instead of raw values. Bulk export commands are blocked. The agent gets scoped access instead of full environment inheritance.

Leak #6 — The vector nobody's talking about

6. Tool-use with credentials High

This is the leak vector nobody's talking about yet. 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 decides to log 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. They're 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 agent should say "query the database" and the MCP server should handle authentication independently.

The pattern

All six leaks share a 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 solution 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.

Install the DLP guard in 30 seconds

The DLP guard: 30 seconds to install

The NoxKey DLP guard scans agent output against 8-character fingerprints of every secret in your Keychain. If any output contains a value matching a stored secret, it blocks the output before it enters the conversation.

# Install the guard
noxkey guard install

# It runs automatically as a PostToolUse hook in Claude Code.
# No config. No setup beyond the install command.

# What it looks like when it catches something:
#
# Agent runs: curl -v https://api.stripe.com/v1/charges
# Agent output contains: "Authorization: Bearer sk_live_51ABC..."
# Guard: BLOCKED — matches myorg/project/STRIPE_KEY
# Output is redacted before entering context.
#
# You see: [REDACTED — credential detected]
# The agent sees: nothing. The secret never entered the conversation.

This catches leaks 2, 3, and 4 — debug output, conversation logs, and generated code that includes credential values. It doesn't prevent the agent from accessing .env files or inheriting environment variables (leaks 1 and 5), which is why eliminating .env files and using encrypted handoff are still necessary.

The guard isn't perfect. It matches on 8-character prefixes, so extremely short secrets or secrets that share prefixes with common strings could produce false positives. In practice, API keys and tokens are long enough that this isn't an issue. I've been running it for months with zero false positives across ~200 stored secrets.

What to do right now

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

  1. Delete your .env files. Move secrets to the Keychain. This eliminates leak #1 entirely. (Here's how I did it.)
  2. Install the DLP guard. One command. Catches leaks #2, #3, and #4 automatically.
  3. Use encrypted handoff. Process-tree detection + encrypted responses prevent leak #5.
  4. Audit your MCP configs. Remove hardcoded credentials from tool server configurations.
  5. Stop pasting credentials into chats. Just stop. There's no technical fix for voluntarily sending secrets to a third party. Build the habit.
# Install NoxKey and the DLP guard — 30 seconds total
brew install no-box-dev/noxkey/noxkey
noxkey guard install
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, deliver them via encrypted handoff, and install the DLP guard to catch any values that slip through. It takes 30 seconds.

Install the DLP guard. It takes 30 seconds. Your secrets will thank you.