Repository navigation
Description
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
PreToolUsehook 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 norespectGitignoresetting 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
.txtfile that happens to
contain sensitive values) - Requires the user to know the
FileChangedhook 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
.gitignoreby default for FileChanged. Add a setting
fileChanged.respectGitignorethat defaults to true. Files in
.gitignoreshould 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 keysAKIA[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: falsefor 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
.envfile 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
- Claude Code exposes secrets from .env / .dev.vars files via grep -n and Read tool, despite CLAUDE.md prohibitions #44868 — first (model-side) exposure vector via
grep -nand 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.mdwith explicit handling rules for
sensitive files - User has
~/.claude/hooks-guard-secrets.mjsPreToolUse hook installed
and verified working againstRead/Bash/Grep/Globtool calls - Despite all of the above, sensitive file contents still leaked via
FileChanged - Workaround
~/.claude/hooks-block-filechanged-secrets.mjswritten and
deployed during the session as an emergency mitigation
Activity
RCushmaniii commentedon Apr 7, 2026
Incident Report: Claude Code Repeated Credential Exposure
Reporter: Robert F. Cushman III (CushLabs AI Services)
Date: 2026-04-07
Product: Claude Code (Anthropic CLI), Claude Opus 4.6 (1M context)
Project: ny-english-messenger-bot — Cloudflare Worker, client engagement
Related issues: #44868, #44909
Severity: High — recurring, undetected, default-on credential exposure in a paid developer product
Summary
In a single Claude Code session on 2026-04-07, the same user's API credentials were exposed into conversation history multiple times in roughly four hours, via two distinct mechanisms. Each exposure required a credential rotation. No production code was written and no deployment occurred during the session. The exposures continued after the user installed the documented mitigation (a
PreToolUseguard hook), because one of the failure modes is not gated by tool calls and the hook cannot see it.This is a representative incident in a longer pattern that has been visible to this user across many prior sessions.
Failure modes observed
1. Model-side: inspection commands print secret file contents
The model issued
grep -n curl .dev.varsto locate a malformed line in a.dev.varsfile the user had just edited.grep -nprints the matching line content along with line numbers. The matching line contained a real Cloudflare API token. The token was written to the conversation transcript and persisted in chat history.This is the failure mode tracked in #44868. The same family includes
cat,head,tail,awk,sed,xxd,strings,printf, and anygrep/rginvocation without-c/--count.Mitigations the user already had in place:
~/CLAUDE.mdrule prohibiting reading or echoing secret files~/.claude/hooks-guard-secrets.mjsPreToolUse hookWhy none of it caught the leak:
CLAUDE.mdrule is advisory text. The model knew the rule and ran the command anyway, then apologized after the fact.cat/head/tailbut did not listgrep,awk,sed,xxd, orstrings. It also matched only.env/.pem/.keypaths and did not know about.dev.vars.2. Harness-side:
FileChangednotifications inject file contents into contextAfter the user rotated the exposed Cloudflare token and pasted the new value into
.dev.vars, the act of saving the file in the editor triggered Claude Code'sFileChangednotification mechanism. The harness read the modified lines and emitted a system reminder containing the full contents of those lines, including:No tool call was made. No permission was requested. No hook fired. The user pressed Ctrl+S in their editor and the contents of the file were piped into the model's context as a system reminder, where they were persisted in conversation history.
This is the failure mode tracked in #44909.
Mitigations the user had in place:
PreToolUsehook from earlier in the session.dev.varslisted in.gitignoreWhy none of it helped:
PreToolUsehooks only fire on tool calls.FileChangednotifications are not tool calls..gitignoreis not consulted by the file watcher. There is norespectGitignoresetting that applies.FileChangedcontent injection.Sequence of events
.dev.varsgrep -n curl .dev.varsto locate the bad line.dev.varsFileChangedsystem reminder containing lines 15–23 of.dev.varsPreToolUseguard hook to closegrep/awk/sed/.dev.varsgapsFileChangedblocking hook, wires it into settings.json, tests itCredentials requiring rotation as a direct result of this session:
Productive development time during the session: zero. No code was written, no infrastructure was provisioned, no deployment occurred.
Root cause analysis
Both failure modes share one structural cause: Anthropic ships Claude Code with no harness-level enforcement against credential exposure. Every defensive mechanism documented to the user —
CLAUDE.mdrules,.gitignore, model self-monitoring — is advisory or operates at the wrong layer to catch the actual leaks.CLAUDE.mdrules.gitignorePreToolUsehooksFileChangedFileChangedhooksThere is no default-on, harness-enforced filename filter. There is no default-on output scrubber. There is no setting to disable
FileChangedcontent injection. There is no documentation warning users that editing a.envfile in their editor will leak its contents into chat history.Mitigations the user implemented during the session
These are the user's own work, performed mid-session with the agent's assistance. They are not Anthropic-supplied.
~/.claude/hooks-guard-secrets.mjs— full rewrite. Blockscat,head,tail,less,more,type,get-content,gc,select-string,sed,awk,xxd,od,strings,base64,cp,mv,scp,rsync,curl,wget,printf,echoagainst any path matching sensitive-file patterns. Allowsgrep/rgonly with-c/--count. Allowswc,stat,ls. Exempts.example/.sample/.templatesuffixes. Verified against 8 reproduction cases.~/.claude/hooks-block-filechanged-secrets.mjs— new file. Wired into theFileChangedevent in~/.claude/settings.json. BlocksFileChangednotifications for sensitive-file patterns. Verified against 5 reproduction cases.~/CLAUDE.md— paragraph added under "Secrets & Sensitive Data" documenting the existence of both hooks and instructing future Claude instances not to attempt to bypass them.Repo-level memory —
feedback_secrets_handling.mddocumenting the failure mode and the safe-command alternatives, so future sessions in this repository inherit the knowledge.These mitigations protect this user in this user's environment going forward. They do not protect any other Claude Code customer. Default behavior on a clean install of Claude Code remains exposed.
Recommended actions for Anthropic
Default-on filename filter for
FileChangedcontent injection. When a modified file matches a sensitive-pattern path (.env*,.dev.vars*,*.pem,*.key,id_rsa*,credentials*.json,service-account*.json, anything containingsecret/token/credential), the harness should emit metadata only, not content.Default-on output-side scrubber. Before any tool result or system reminder is appended to conversation history, run a regex pass over the content for known token shapes (
sk-,sk-ant-,cfut_,ghp_,gho_,xox[bp]-,AKIA[0-9A-Z]{16}, JWT shapes, PEM block markers) and replace matches with<REDACTED>placeholders.Respect
.gitignorefor the file watcher. Ship afileChanged.respectGitignoresetting defaulting totrue. Files ignored by git should not have their contents injected into the conversation transcript without explicit opt-in.User-facing kill-switch. Add
fileChanged.includeContent: falsefor users who want metadata-only notifications regardless of filename pattern.Documentation. Add a prominent security notice to the Claude Code docs explaining that the file watcher reads modified files and pipes their contents into the model's context, and how to disable or filter this.
Notification of affected users. Users whose past Claude Code sessions touched sensitive-pattern files should be notified so they can rotate proactively.
Audit and purge mechanism. Provide a way for affected users to identify which past transcripts contain leaked file contents, and to request purge from server-side storage and downstream telemetry.
Customer credit for incident-related cleanup time. Establish a policy for compensating customers whose paid sessions are consumed by mitigating Anthropic-side defects.
What this user is asking for
A response from Anthropic acknowledging:
A holding response within seven days. A substantive response within thirty.
This document was prepared by Claude Opus 4.6 in the same Claude Code session in which the incidents described above occurred, at the user's instruction. Technical claims are reproducible from the user's environment and from the public bug reports cited.
github-actions commentedon May 20, 2026
Closing for now — inactive for too long. Please open a new issue if this is still relevant.
github-actions commentedon Jul 10, 2026
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.