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
- Set up a Claude Code session in a project directory.
- User edits
.dev.vars (or .env, .env.local, credentials.json, etc.)
in their editor, putting real API tokens in it. File is in .gitignore.
- User saves the file.
- 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>
- 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:
- No filename-based filter (no exclusion for
.env*, .dev.vars*, etc.).
- No content-based scrubber (no detection of high-entropy strings before
the reminder is composed).
- No respect for
.gitignore (the watcher fires on gitignored files).
- No user-facing setting to disable or scope the feature.
- 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)
-
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").
-
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.
-
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.
-
User-facing setting to disable FileChanged content entirely. Add
fileChanged.includeContent: false for users who want metadata-only
notifications regardless of filename.
-
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.
-
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
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
Summary
Claude Code has a second, distinct exposure vector beyond the one
already filed in #44868. The
FileChangednotification mechanism — whichpushes 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 transcriptwithout any tool call ever being made.
This bypasses every model-side safeguard:
never gets a chance to refuse, because no decision is happening on its end).
PreToolUsehook configured to block sensitive-fileinspection (the hook only fires on tool calls; this leak path is not a
tool call).
.gitignore(the watcher fires regardless of gitignore status,with no
respectGitignoresetting 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
.dev.vars(or.env,.env.local,credentials.json, etc.)in their editor, putting real API tokens in it. File is in
.gitignore.to the model that includes the contents of the changed lines, formatted
as:
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.varsfiletwice 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
FileChangedevent emits content, not just metadata. When a fileis modified during a session, the harness reads the file (or the changed
lines) and pipes them into a system reminder verbatim. There is:
.env*,.dev.vars*, etc.).the reminder is composed).
.gitignore(the watcher fires on gitignored files).editor will leak the contents into chat history.
Why the existing safeguards do not help
CLAUDE.mdrule "never output sensitive values"PreToolUsehook (e.g.~/.claude/hooks-guard-secrets.mjs).gitignoreentry for the sensitive filerespectGitignoresettingWorkaround (incomplete)
Claude Code exposes a
FileChangedhook event that can return ablockdecision, 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/.templateexemptions). The script is ~60 lines of Node and is wired into the
FileChangedevent 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.mdrules in place.Most users will never know it happened.
Additionally, this workaround:
.txtfile that happens tocontain sensitive values)
FileChangedhook event exists (it isnot documented as a security mechanism)
the leak vector exists
Proposed fix (defense in depth, harness-level)
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 commonpatterns. Replace with metadata-only ("file changed, N lines, contents
redacted because filename matches sensitive pattern").
Respect
.gitignoreby default for FileChanged. Add a settingfileChanged.respectGitignorethat defaults to true. Files in.gitignoreshould not have their contents injected into theconversation transcript without an explicit opt-in.
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 keysAKIA[0-9A-Z]{16}, JWT shapes,PEM block markers, etc.) and replace with
<REDACTED:reason>. Themodel should see the redaction marker, not the value.
User-facing setting to disable FileChanged content entirely. Add
fileChanged.includeContent: falsefor users who want metadata-onlynotifications regardless of filename.
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.
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 -nmodel-side leak), and a second timebecause 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:
.envfile is a potential incident.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.
to Anthropic's servers, and likely retained in telemetry beyond the
user's direct control.
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
grep -nand similarinspection 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.mdwith explicit handling rules forsensitive files
~/.claude/hooks-guard-secrets.mjsPreToolUse hook installedand verified working against
Read/Bash/Grep/Globtool callsFileChanged
~/.claude/hooks-block-filechanged-secrets.mjswritten anddeployed during the session as an emergency mitigation