Claude Code, Codex and Cursor can read your .env. Here’s how to stop it — and why deny rules aren’t enough.
What each tool’s own setting blocks, what slips past it, and the checklist that actually keeps keys out of the chat.
Ask Claude Code, Codex or Cursor to fix a failing payment, and one of the first things it will do is look around your project. Your .env file sits in the root, next to package.json. It holds your Stripe secret key, your database password and your OpenAI key. Nothing stops the AI from opening it — and once it does, those values are in the conversation.
That matters for three reasons. The conversation goes to the model provider. The AI may paste a value it has seen into code, a test, or a command. And whatever it pasted can end up in a commit. GitGuardian’s State of Secrets Sprawl 2026 found that public commits made with Claude Code leaked a secret at 3.2%, against 1.5% for all public commits — roughly twice the rate.
Every tool has a setting that is supposed to help. Each one blocks less than its name suggests.
What each tool’s own setting blocks
Claude Code: deny rules
Claude Code lets you deny the file-reading tool on chosen paths, in .claude/settings.json:
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)"
]
}
}
This stops the built-in Read tool. Claude Code’s permissions documentation is explicit that it does not reliably stop the shell: Read and Edit deny rules apply to Claude’s own file tools, “not to Bash subprocesses”, so a cat .env, jq . .env or a one-line Python script that opens the file can go through the Bash tool instead. Recent versions also refuse some shell commands whose arguments name a denied path (issue #45200), but a script that builds the path itself still walks past. The documented answer is the sandbox, which enforces file access at the operating-system level for every process the session starts. Turn it on if you can.
Cursor: .cursorignore
A .cursorignore file keeps the listed files away from Cursor’s agent, Tab, inline edits and @-mentions. Cursor’s own documentation says the terminal and MCP tools the agent uses cannot be blocked by it, and that complete protection isn’t guaranteed. When the agent runs cat .env in its terminal, the ignore file doesn’t apply.
Codex: the sandbox
Codex runs commands in a sandbox that limits where it may write and whether it may reach the network. Reading the files in your project is allowed — Codex needs that to work — and your .env is a file in your project.
Every built-in setting matches a tool or a file name. A key can be read by a dozen different commands, and it can live in a dozen places besides .env: ~/.aws/credentials, docker-compose.yml, the shell’s environment (env, printenv), a CI file, a config written by a CLI login. Rules on names catch the obvious case and miss the rest.
Why “just deny it” isn’t enough
Reading a key is not the dangerous moment. Sending it is. A key the AI has read is a problem; a key the AI has put in a curl header, written into a test file, or committed is an incident. A setting that only looks at which file was opened can’t tell those apart, and can’t tell you afterwards which keys were exposed.
So the useful check looks at what a command does: does it read a key, does it send data out, does it do both at once.
The checklist that works with any tool
- Keep production keys off your laptop. Use test keys locally, with spending limits. A test Stripe key that leaks costs you a rotation, not a payout.
- Keep the built-in settings on anyway. Claude Code deny rules plus the sandbox, a
.cursorignore, Codex’s default sandbox. They stop the easy cases for free. - Check commands, not just files. In Claude Code a PreToolUse hook runs before every command, whatever the permission mode, and can refuse it. A minimal one that refuses any shell command touching an env file. In
.claude/settings.json:{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [{ "type": "command", "command": "~/.claude/hooks/no-env.sh" }] } ] } }
Exit code 2 refuses the command and tells Claude why. It is a start, not a wall: it still matches a name, and#!/bin/sh # ~/.claude/hooks/no-env.sh — refuse shell commands that touch .env files cmd=$(jq -r '.tool_input.command // ""') if printf '%s' "$cmd" | grep -Eq '(^|[^A-Za-z0-9_])\.env([^A-Za-z0-9_]|$)'; then echo "Reading .env files is not allowed. Ask for the key's name instead." >&2 exit 2 fi exit 0printenvwalks right past it. - When a key was read, rotate it. Assume anything that reached the conversation is exposed. Create the new key, deploy it, then revoke the old one.
- Watch usage at the provider. A stolen key is used from someone else’s computer, so nothing in your project changes. The provider’s usage numbers do — here is how to read them in five minutes.
What ArgosBrain does here
ArgosBrain’s Secret Guard runs inside Claude Code, Codex and Cursor and reads each command for what it does with keys, not for a file name. For each kind of event you choose warn, ask or block. The defaults:
| When the AI… | Default |
|---|---|
| reads a key into the conversation | Warn |
| writes a key into a code file | Warn |
| sends a key over the network | Ask |
| reads keys and sends data out in one command | Block |
| commits a secret file to git | Block |
| changes its own hook settings | Ask |
Claude Code can ask you; Codex and Cursor can’t answer a question from a check like this, so for them “ask” means the command is refused, and the AI is told it can ask for key names instead of values. Every event is listed with an Explain button: what happened, whether there is any risk now, what to do. The key map shows which part of your project reads which key and which service it unlocks, so when one must be rotated you know everywhere it lives.
What it can’t do: see programs that are not the AI — a package’s install script, a build tool — or clean a key that is already in your git history.
Sources
GitGuardian, The State of Secrets Sprawl 2026
Cursor, Ignore file
Claude Code issue tracker, #45200: Read deny rules and Bash, quoting the permissions documentation