Stop Claude Code reading your .env with a Read deny rule
A Read deny rule is the reliable way to keep .env out of the model's input. How to write the pattern, how depth and anchoring change its reach, and where deny stops working.
Your .env holds API keys and database passwords. Claude Code reads files inside the working directory without asking, so by default the contents of .env end up in the model's input.
The reliable way to stop that is a Read deny rule in settings.json. Deny rules are evaluated before allow rules and block regardless of permission mode.
KEY POINT
What you will learn
- The deny rule that keeps
.envout of reach - How pattern shape changes what the rule actually covers
- Where deny rules do not reach, and what to use instead
The rule
Put this in your project's .claude/settings.json:
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)"
]
}
}
Read(./.env.*) covers derived files such as .env.local and .env.production. A Read deny rule also blocks the Edit and Write tools on the same path, including creating a new file there, so these three lines stop both reading and rewriting. NotebookEdit is not covered — add an Edit deny rule for paths no tool may change.
用語解説
Deny precedence: rules are evaluated in order — deny, then ask, then allow — and the first match determines the outcome. Rule specificity does not change that order, so an allow rule cannot carve an exception out of a deny rule.
Pattern shape decides the anchor
Read and Edit rules use gitignore pattern syntax. The leading characters decide where the pattern is anchored.
| Pattern | Anchored at | Example |
|---|---|---|
path or ./path | Current directory | Read(./.env) |
/path | The settings source that defines it | Read(/config/.env) |
~/path | Home directory | Read(~/.aws/credentials) |
//path | Filesystem root | Read(//etc/shadow) |
Depth is a separate question. Bare filenames follow gitignore semantics and match at any depth:
| Deny rule | Blocks | Does not block |
|---|---|---|
Read(./.env) | .env in the current directory | .env in a subdirectory |
Read(.env) or Read(**/.env) | any .env at or under the current directory | .env in a parent directory or another project |
Read(//**/.env) | any .env anywhere on the filesystem | nothing; it is anchored at the filesystem root |
In a monorepo where packages/api/.env and packages/web/.env both exist, Read(./.env) is not enough. Write Read(.env) to match at any depth.
The anchor moves when you write the rule in user settings
Read(/secrets/**) in ~/.claude/settings.json anchors at the settings source, so it matches ~/.claude/secrets/** — not your project's secrets directory. A single leading slash is not an absolute path. To cover every project, use // for a filesystem-absolute path or ~/ for a home-relative one.
Where deny rules reach
Read and Edit deny rules apply to:
- Claude's built-in file tools (Read, Edit, Write), with Grep, Glob,
@filementions in your prompt and IDE-shared open files covered where possible - File commands Claude Code recognizes in Bash:
cat,head,tail,sedandtee. These normally run without a prompt, but a deny rule blocks them - Bash redirection targets, both
> fileand< file. Input-target checking requires Claude Code v2.1.257 or later
Symlinks are checked on both paths — the link and its target — and a deny rule applies when either one matches.
Scripts that open files themselves are not covered
Deny rules do not apply to a command that reads files without naming them, such as grep -r pattern . run from the directory holding the file, or to a Python or Node script that opens files itself. python -c "print(open('.env').read())" is just a command execution as far as the permission checker is concerned. For OS-level enforcement that blocks all processes, enable the sandbox.
Note also that the protected paths list — the set of writes never auto-approved — includes .envrc, not .env, and covers writes only. Reads need your own deny rule.
Confirming it works
- Save the settings and start Claude Code.
- Run
/permissions. It lists the active rules and which settings file each came from. - Ask Claude to read
.env. You should getFile is covered by a Read deny rule in your permission settings.
If you wrote a pattern that can't be used for file checks, Claude Code warns at startup with ... is not matched by file permission checks. That warning means the path syntax needs fixing — for example, a path rule written for Write, NotebookEdit or Glob, which Claude Code accepts but never consults.
For the wider rule design, see Claude Code permissions in settings.json. For keeping secrets away from all three tools, see Stop AI coding tools from reading your API keys and secrets.
Summary
- Put
Read(./.env)andRead(./.env.*)inpermissions.denyin.claude/settings.json - A
Readdeny also covers Edit and Write, but not NotebookEdit Read(./.env)is current-directory only;Read(.env)matches at any depth- Deny beats allow and applies in every mode, including
bypassPermissions - Scripts that open files themselves slip past it — use the sandbox for OS-level enforcement
FAQ
- Does a deny rule also stop cat .env?
- Yes. Read deny rules apply to file commands Claude Code recognizes in Bash — cat, head, tail, sed and tee — and to Bash redirection targets. They do not apply to a Python or Node script that opens the file itself.
- Does deny still apply under --dangerously-skip-permissions?
- Yes. Deny rules block in every mode, including bypassPermissions. Allow rules, by contrast, have no effect in that mode.
- Isn't .env protected already?
- No. The protected paths list includes .envrc but not .env, and it only covers writes. Stopping reads requires a deny rule you write yourself.
Primary sources
This article was drafted by AI from official documentation and reviewed by the site operator before publishing. Found a mistake? Let us know via the contact page.