Skip to content

Build1 publisher3 min readPublished

A memory note is not a gate: blocking credential reads with a PreToolUse hook

A developer leaked live keys into agent transcripts three times before replacing a polite memory note with a hook that denies the read outright.

The Engineer · Build desk

Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction

Illustration accompanying A memory note is not a gate: blocking credential reads with a PreToolUse hook
Generated illustration

What happened

  • The author states that three times a Claude Code session read a file full of live credentials into its own conversation transcript, not maliciously and not on request.
  • In each incident the agent ran cat or grep on something to 'check a value', and the whole file including secrets ended up in plaintext in a log whose retention the author says he will never fully control.
  • Each incident cost the author rotating every credential in the affected file, one dashboard at a time, while whatever depended on them kept running on borrowed time.
  • cat, cat -A and a plain grep PATTERN file all print the matched line in full, so a line such as ANTHROPIC_API_KEY=sk-ant-... becomes part of both the context window and the transcript.
  • After the first incident the author wrote a memory note: do not cat .env files, use cut -d= -f1 to list keys instead, use grep -c for presence checks.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

A developer writing on dev.to reports that three separate Claude Code sessions read files of live credentials straight into their own conversation transcripts, each time because someone (the author, or the agent acting on its own initiative) reached for `cat` or `grep` to check a value [1] [2]. The consequence is not an abstract risk surface: each incident meant rotating every credential in the file, one dashboard at a time, while everything depending on those keys kept running on borrowed time [3].

The mechanism is boring, which is why it keeps working. `cat`, `cat -A`, and a plain `grep PATTERN file` all print the matched line in full, so a line reading `ANTHROPIC_API_KEY=sk-ant-...` lands in both the context window and the transcript [4]. The author notes the transcript is a log whose retention they will never fully control [2].

The first response was the one most teams reach for: a memory note saying do not `cat` .env files, use `cut -d= -f1` to list key names, use `grep -c` for presence checks [5]. It failed twice. The second leak came through a `.toml` file the author had not thought to add to the list [6]. The third came from a different session that used the `Read` tool rather than a shell command, which the rule did not cover at all [7]. The author's diagnosis is that they had written a "do not run these commands" rule when the actual requirement was "do not touch these files, by any means" [8], and that a memory note is advice the model re-derives probabilistically each time, covering only the tools and patterns you happened to imagine, and only in sessions that loaded it [9].

The replacement is a `PreToolUse` hook: shell commands the harness runs at defined lifecycle points, deterministically, every time, with the ability to return a decision that blocks the call before it executes [10] [11]. The script reads the pending tool call as JSON on stdin, tests the target path against twelve regexes covering .env variants (while exempting example, sample, template and dist), `id_rsa` and `id_ed25519` private keys but not their `.pub` counterparts, `.pem`, `credentials.json`, `secrets.*`, `.key`, `frp*.toml`, and the shell dotfiles [12] [13] [14]. For `Read`, the path match is the entire check [15]. For `Bash`, it requires two conditions, a secret-looking path plus a command that would actually print contents (`cat`, `head`, `tail`, `sed`, `less`, `awk`, or a bare `grep` without `-c`), so `ls -la .env` and `chmod 600 .env` still pass [16]. Denial is JSON on stdout with `permissionDecision: deny` and a reason that names the safe alternative, and it is wired into `settings.json` under both the `Bash` and `Read` matchers [17] [18].

Two limits are worth stating plainly. The hook is still an enumerated list, so a credential file matching none of the twelve patterns is not blocked; the gain is enforcement, not completeness [19]. And it is scoped to two matchers, so any other path to file contents, whether an edit tool, an MCP server, or a subagent, is ungated until someone adds it [20]. The author's own history is the argument for checking: the failure mode each time was a tool nobody had listed [6] [7].

Worth noting for anyone citing the post: the headline describes rotating the same five keys twice, while the body describes three incidents [21] [1].

Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories