Built-in guard against Claude reading/printing credential-file contents (.env, shared.env, etc.)
Title: Built-in guard against Claude reading/printing credential-file contents (.env, shared.env, etc.)
Summary
Request a first-party safeguard in Claude Code that blocks the model from executing tool calls that would read the full contents of an obvious credential file (.env, .env.*, files containing secret/credential, etc.) via unsafe patterns — cat/head/tail/less/more, a grep without -o, or the Read tool — so a printed secret becomes structurally harder rather than depending on the model remembering a written instruction every time.
What happened
Over one working relationship, the same failure shape recurred multiple times despite explicit, repeated project-level instructions (in CLAUDE.md) telling Claude never to Read a credential file directly and always to extract single values via grep -o '^KEY=.*' file | cut -d= -f2-, never a bare cat/grep:
- Once via the
Readtool used directly on a.envfile (should have used a safe extraction command instead). - Twice via a
grepcommand that omitted the-oflag (e.g.grep -n "SOME_KEY" .env), which prints the entire matching line —KEY=value— including the secret value, instead of just the key name.
Each time, the credential then had to be treated as compromised and rotated, since it had been printed in full into the conversation transcript.
Why a project-level instruction wasn't enough
CLAUDE.md-style instructions are text the model is expected to recall and apply correctly on every single tool call, in the moment, regardless of what else it's focused on in that turn. That's a single point of failure — one lapse (e.g. reaching for a familiar grep pattern while focused on a different task) fully defeats it, and it happened more than once in the same documented shape even after the first occurrence was written up as a lesson-learned.
Workaround currently in place
As a stopgap, I had Claude build a local PreToolUse hook (Bash + Read matchers) that pattern-matches command text / file paths against common credential-file naming conventions (.env, .env.*, shared.env, *credential*, *secret*) and denies the tool call outright if it would cat/head/tail/less/more the file or grep it without -o. This works, but it's user-space config: something I had to know to ask for, that lives only on this machine, that could be silently deleted/disabled/misconfigured, and that a new user wouldn't have by default.
Feature request
Build the equivalent of this protection into Claude Code itself, ahead of the point where a tool call result reaches the model — e.g.:
- Detect common credential-file naming patterns (
.env/.env.*,*secret*,*credential*, cloud-provider key file conventions) and refusecat/head/tail/less/more/unflagged-grep/rawReadagainst them by default, independent of any user-authored hook. - Surface a clear, actionable error back to the model (not just a silent failure) so it can self-correct to the safe extraction pattern, same behavior a well-written hook already provides.
- Ideally also cover common secret-shaped strings appearing in other tool outputs (e.g. an API response body that happens to embed a token) — this is a related but distinct gap a Bash/Read-specific guard doesn't close, and was a separate incident in my own case.
Happy to share more detail on the exact recurrence pattern / hook implementation if useful for scoping this.