Skip to content

Conversation history leak via FileChanged notifications bypasses guard hooks and gitignore #44909

@RCushmaniii

Description

@RCushmaniii

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.
  3. User saves the file.
  4. 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>
    
  5. 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).
  3. No respect for .gitignore (the watcher fires on gitignored files).
  4. No user-facing setting to disable or scope the feature.
  5. 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").

  2. 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.

  3. 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.

  4. User-facing setting to disable FileChanged content entirely. Add
    fileChanged.includeContent: false for users who want metadata-only
    notifications regardless of filename.

  5. 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.

  6. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions