An agent that can read any file in your repo can also read .env, and whatever it reads can end up in a prompt, a log, or a commit message. You don't need to trust it to behave. You can just say no in settings.json.
The rules
Put this in .claude/settings.json at the repo root so it applies to everyone who opens the project:
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)"
]
}
}
Deny rules win over allow rules, so even if someone later adds a broad Read allow, these files stay off limits.
Things people miss
-
Example files are fine. If you keep a
.env.example, it's caught by./.env.*too. Rename it toenv.exampleor add it back with a narrower pattern. -
Shell is a side door. A deny on
Readdoesn't stopcat .envthrough Bash. Keep Bash on an allow-list of specific commands instead of allowing everything. -
Commit the file.
.claude/settings.jsonis meant to be checked in. Personal overrides go in.claude/settings.local.json. - Check your version. Permission rule syntax has changed between Claude Code releases, so confirm against the settings docs for the version you run.
Test it
Ask the agent to "show me the contents of .env". It should refuse with a permission message rather than print anything. If it prints, your rule path doesn't match.
I ship this deny list together with a Bash allow-list, safety hooks, and a short CLAUDE.md in Vildanden Agent Config. The free sample is enough to try the pattern today.
Top comments (1)
Ran into exactly this gap last month. The three deny rules are a solid baseline, but the Bash bypass (
cat .env,jq . .env) is the actual leak — if you don't gate shell commands with the same deny patterns, the guardrail is theater. We commit.claude/settings.jsonper-project so every teammate inherits protection; a global config misses new repos and creates false confidence. The self-test isn't a one-and-done — run it monthly because point releases quietly change syntax and silently drop rules. Our near-miss: an agent pasted an AWS key into a debug log while troubleshooting a deploy. That taught us to extend denies to*.pem,id_*,*.key, andsecrets/**patterns. Also block.env.exampleexplicitly if you store template values there — otherwise the agent refuses to read the example file you actually need it to reference.