Conversation history leak via FileChanged notifications bypasses guard hooks and gitignore

Resolved 💬 3 comments Opened Apr 7, 2026 by RCushmaniii Closed May 20, 2026

Summary

Claude Code has a second, distinct exposure vector beyond the one
already filed in #44868. The FileChanged notification mechanism — which
pushes the contents of modified files into the model's context as a system
reminder whenever a file is saved during a session — leaks the full line
contents
of sensitive files (.env, .env.local, .dev.vars,
credentials.json, *.pem, *.key, etc.) into the conversation transcript
without any tool call ever being made.

This bypasses every model-side safeguard:

  • It bypasses the model's CLAUDE.md instructions (advisory text — the model

never gets a chance to refuse, because no decision is happening on its end).

  • It bypasses any PreToolUse hook configured to block sensitive-file

inspection (the hook only fires on tool calls; this leak path is not a
tool call).

  • It bypasses .gitignore (the watcher fires regardless of gitignore status,

with no respectGitignore setting that applies to it).

The user does not click anything. The user does not run a Claude Code
command. The user simply saves a file in their editor. The Claude Code
harness then injects that file's contents into the model's context, where
it is persisted in conversation history, transmitted to Anthropic's
servers, and retained indefinitely beyond the user's direct control.

Reproduction

  1. Set up a Claude Code session in a project directory.
  2. User edits .dev.vars (or .env, .env.local, credentials.json, etc.)

in their editor, putting real API tokens in it. File is in .gitignore.

  1. User saves the file.
  2. Claude Code's harness detects the file change and emits a system reminder

to the model that includes the contents of the changed lines, formatted
as:
``
The user selected the lines N to M from <path>:
<FULL CONTENTS OF LINES N TO M, INCLUDING SENSITIVE VALUES>
``

  1. The reminder is part of the conversation transcript. The sensitive values

are now in chat history. No tool call was made. No permission was asked.
No hook fired.

Actual reproduction in a real session today (anonymized):

A user working on a Cloudflare Workers project saved their .dev.vars file
twice in the same session (once after pasting an Anthropic API key, once
after rotating a Cloudflare API token that had been exposed via the
unrelated issue described in #44868). Both saves triggered FileChanged
notifications that injected the full contents of the relevant lines —
including the freshly-rotated Cloudflare token and the Qdrant API key —
into the conversation transcript.

The user then had to rotate the Cloudflare token a second time, and rotate
the Qdrant key for the first time
, in the same 30-minute window. Both
rotations were caused entirely by the harness's own behavior, not by any
action the user or the model took.

Root cause

The FileChanged event emits content, not just metadata. When a file
is modified during a session, the harness reads the file (or the changed
lines) and pipes them into a system reminder verbatim. There is:

  1. No filename-based filter (no exclusion for .env*, .dev.vars*, etc.).
  2. No content-based scrubber (no detection of high-entropy strings before

the reminder is composed).

  1. No respect for .gitignore (the watcher fires on gitignored files).
  2. No user-facing setting to disable or scope the feature.
  3. No documentation warning users that editing sensitive files in their

editor will leak the contents into chat history.

Why the existing safeguards do not help

| Safeguard | Helps? | Why not |
|---|---|---|
| CLAUDE.md rule "never output sensitive values" | No | Model is not deciding to output them — the harness already did, before the model saw the prompt. |
| PreToolUse hook (e.g. ~/.claude/hooks-guard-secrets.mjs) | No | Hook only fires on tool calls. FileChanged is not a tool call. |
| .gitignore entry for the sensitive file | No | Watcher does not respect gitignore. |
| respectGitignore setting | No | The setting only applies to the file picker, not the file watcher. |
| User being careful | No | The leak fires on save, which the user does in their editor without any awareness Claude Code is watching. |

Workaround (incomplete)

Claude Code exposes a FileChanged hook event that can return a block
decision, suppressing the notification before the model sees it. I have
implemented and tested a working hook that blocks FileChanged notifications
for sensitive-pattern filenames (.env*, .dev.vars*, *.pem, *.key,
credentials*.json, service-account*.json, anything containing the words
"secret"/"token"/"credential", with .example/.sample/.template
exemptions). The script is ~60 lines of Node and is wired into the
FileChanged event in ~/.claude/settings.json.

This workaround should not be necessary. Every Claude Code user is
exposed to this leak by default the moment they edit any sensitive file in
their editor, regardless of whether they have CLAUDE.md rules in place.
Most users will never know it happened.

Additionally, this workaround:

  • Is filename-based only (cannot catch a .txt file that happens to

contain sensitive values)

  • Requires the user to know the FileChanged hook event exists (it is

not documented as a security mechanism)

  • Requires the user to be technical enough to write a Node hook script
  • Does nothing for users who have not yet been bitten and are unaware

the leak vector exists

Proposed fix (defense in depth, harness-level)

  1. Default-deny FileChanged content for sensitive-pattern paths. Ship a

built-in filename allowlist/denylist. The harness should refuse to
include file contents in FileChanged notifications when the path
matches any of: .env*, .dev.vars*, *.pem, *.key, id_rsa*,
credentials*.json, service-account*.json, and other common
patterns. Replace with metadata-only ("file changed, N lines, contents
redacted because filename matches sensitive pattern").

  1. Respect .gitignore by default for FileChanged. Add a setting

fileChanged.respectGitignore that defaults to true. Files in
.gitignore should not have their contents injected into the
conversation transcript without an explicit opt-in.

  1. Output-side scrubber. Before any tool result OR system reminder

is appended to the conversation, run a regex pass for high-entropy
strings and known token prefixes (sk-, sk-ant-, cfut_, ghp_,
gho_, xox[bp]-, AWS access keys AKIA[0-9A-Z]{16}, JWT shapes,
PEM block markers, etc.) and replace with <REDACTED:reason>. The
model should see the redaction marker, not the value.

  1. User-facing setting to disable FileChanged content entirely. Add

fileChanged.includeContent: false for users who want metadata-only
notifications regardless of filename.

  1. Documentation. The Claude Code docs currently do not warn users

that editing files during a session can leak their contents into
chat history. This needs to be a prominent warning, not a footnote.

  1. Audit existing transcripts. Users who have been affected need a

way to know which sessions leaked which files, so they can rotate
credentials proactively rather than discovering the leak by accident
(or never discovering it).

User impact

This is not a hypothetical. A real user, in a real session today, was
forced to rotate two production credentials within 30 minutes — once
because of #44868 (the grep -n model-side leak), and a second time
because of this issue (the FileChanged harness-side leak that fired
the moment they saved the rotated value).

For users running Claude Code on client engagements:

  • Every saved .env file is a potential incident.
  • The leak occurs without any visible signal — the user sees no warning,

no permission prompt, no log line. The first sign anything happened
is when (and if) the model later reasons about file contents it
shouldn't know.

  • The exposed values are persisted in conversation history, transmitted

to Anthropic's servers, and likely retained in telemetry beyond the
user's direct control.

  • Rotating an exposed credential mid-engagement is a real cost: minutes

to hours of cleanup, potential service disruption if the old credential
is in use elsewhere, and a credibility hit if a client notices the
rotation in their audit logs without explanation.

Multiplied across the developer base × the percentage of sessions that
touch a sensitive file × the percentage of those that go undetected, the
implicit cost of this bug is substantial. Many of those leaks will
simply never be caught.

Anthropic should treat this as a P0 security issue, not a feature
gap.
Users are being told their values are safe (via documented
CLAUDE.md hooks, .gitignore, PreToolUse guards) and they are not.
Affected users should be notified, the harness should be patched
immediately, and there should be a path to credit users for time
lost to cleanup caused by the agent's own behavior.

Cross-references

  • #44868 — first (model-side) exposure vector via grep -n and similar

inspection commands. Same root cause family ("the model is trusted to
police its own behavior against sensitive output"), different surface.
This issue (FileChanged) is the harness-side counterpart. Both
need to be fixed; fixing only one leaves the other open.

Environment

  • Claude Code CLI on Windows 11 (MINGW64 / Git Bash)
  • Model: Claude Opus 4.6 (1M context)
  • User has a global ~/CLAUDE.md with explicit handling rules for

sensitive files

  • User has ~/.claude/hooks-guard-secrets.mjs PreToolUse hook installed

and verified working against Read / Bash / Grep / Glob tool calls

  • Despite all of the above, sensitive file contents still leaked via

FileChanged

  • Workaround ~/.claude/hooks-block-filechanged-secrets.mjs written and

deployed during the session as an emergency mitigation

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗