Stop Claude Code reading your .env with a Read deny rule

Claude Code Published: Updated:

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.

Verified on Sep 18, 2026 These tools change quickly. Please also check the latest official documentation.
Contents
  1. The rule
  2. Pattern shape decides the anchor
  3. Where deny rules reach
  4. Confirming it works
  5. Summary

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 .env out 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.

PatternAnchored atExample
path or ./pathCurrent directoryRead(./.env)
/pathThe settings source that defines itRead(/config/.env)
~/pathHome directoryRead(~/.aws/credentials)
//pathFilesystem rootRead(//etc/shadow)

Depth is a separate question. Bare filenames follow gitignore semantics and match at any depth:

Deny ruleBlocksDoes 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 filesystemnothing; 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:

  1. Claude's built-in file tools (Read, Edit, Write), with Grep, Glob, @file mentions in your prompt and IDE-shared open files covered where possible
  2. File commands Claude Code recognizes in Bash: cat, head, tail, sed and tee. These normally run without a prompt, but a deny rule blocks them
  3. Bash redirection targets, both > file and < 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

  1. Save the settings and start Claude Code.
  2. Run /permissions. It lists the active rules and which settings file each came from.
  3. Ask Claude to read .env. You should get File 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) and Read(./.env.*) in permissions.deny in .claude/settings.json
  • A Read deny 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.