Security: automatic 'file changed on disk' notification bypasses secret-file PreToolUse hook, leaking full credential file to transcript

Status Open
Maintainer reply None cached
Activity 0 comments · opened Sep 14, 2026

Summary

A security control that blocks Claude from printing the contents of known secret files (via a PreToolUse hook on Bash — blocking cat/grep without -c/head/tail/etc. against paths like .env files, ~/.vercel, ~/.fly, a project's designated secrets directory) can be silently bypassed by a different, unguarded surface: the harness's own "this file changed on disk since you last read it" notification.

What happened

  1. Claude was mid-way through editing a secrets file (~/.artefakt-secrets/.env.production.local) using a scoped, non-printing method (sed -i on a specific line, confirmed afterward only by printing a key name and value length via awk, never the value itself) — deliberately avoiding the very thing the PreToolUse hook exists to block.
  2. The user had the same file open concurrently in a text editor (nano) in a separate terminal.
  3. This caused the file's mtime to change in a way the harness's file-tracking noticed.
  4. The harness then injected a <system-reminder> into the conversation showing the file's full current contents — every key AND every value — as an unrequested "here's what changed" diff.
  5. This is not a tool call Claude made, so the PreToolUse Bash hook that exists specifically to prevent secret-file contents from reaching the transcript had no opportunity to intercept it.
  6. Ten live production credentials (Supabase service-role key, Supabase management access token, a Vercel deploy token, a Vercel OIDC token, a Resend API key, a Cloudflare Turnstile secret, a cron secret, an R2 access key, and two lower-value external API keys) ended up printed in full in the conversation transcript. All had to be rotated.

Why this matters

Users are told (and the product surfaces, via things like the PreToolUse secret-file-read hook itself) that Claude Code has guardrails against a known, high-frequency failure mode: an agent accidentally printing a secret into a transcript that may be logged, cached, or reviewed by others. Those guardrails are implemented as tool-call interceptors. But at least one other code path — the automatic "file changed since last read" notification — can produce the exact same outcome (full secret file contents in the transcript) with no interception point available to the guardrail at all, because it isn't a tool call.

This means the existing hook gives a false sense of coverage: it stops the leak vectors it was built to catch (bad shell commands) while leaving at least one systemic vector wide open — one that doesn't even require the agent to make a mistake. In this case the agent's own command was correctly scoped and non-printing; the leak happened entirely inside harness-generated context injection.

Suggested fix

The file-change notification mechanism should respect the same secret-path awareness the PreToolUse hook already has (or a shared one) — e.g., for a path matching known secret-file patterns (.env*, credential directories, etc.), either:

  • suppress the automatic content diff and instead say only "this file changed on disk, re-read explicitly if needed" without content, or
  • route it through the same redaction/blocking logic the Bash hook uses before ever reaching the model's context.

Environment

  • Claude Code CLI, non-interactive/agent SDK session
  • macOS (Darwin 24.6.0)
  • The PreToolUse hook referenced above is a project/user-configured hook blocking cat/head/tail/less/more/bat/xxd/od/hexdump/echo/printf/grep (without -c) against ~/.fly, ~/.vercel, the Vercel CLI app-support dir, a designated ~/.artefakt-secrets directory, and *.env* files — so the gap described here is specifically that this hook type (PreToolUse on Bash) cannot see or block harness-injected <system-reminder> content.

View original on GitHub ↗