Skip to content

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

Closed as not planned

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

Activity

  1. RCushmaniii commented on Apr 7, 2026

    @RCushmaniii
    Author

    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 PreToolUse guard 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.vars to locate a malformed line in a .dev.vars file the user had just edited. grep -n prints 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 any grep/rg invocation without -c / --count.

    Mitigations the user already had in place:

    • Global ~/CLAUDE.md rule prohibiting reading or echoing secret files
    • Pre-existing ~/.claude/hooks-guard-secrets.mjs PreToolUse hook

    Why none of it caught the leak:

    • The CLAUDE.md rule is advisory text. The model knew the rule and ran the command anyway, then apologized after the fact.
    • The pre-existing PreToolUse hook had gaps: it blocked cat/head/tail but did not list grep, awk, sed, xxd, or strings. It also matched only .env/.pem/.key paths and did not know about .dev.vars.

    2. Harness-side: FileChanged notifications inject file contents into context

    After 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's FileChanged notification mechanism. The harness read the modified lines and emitted a system reminder containing the full contents of those lines, including:

    • Qdrant Cloud URL
    • Qdrant API key (full value)
    • Cloudflare account ID
    • Newly-rotated Cloudflare API token (full value)

    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:

    • Same as above, plus the rewritten PreToolUse hook from earlier in the session
    • .dev.vars listed in .gitignore

    Why none of it helped:

    • PreToolUse hooks only fire on tool calls. FileChanged notifications are not tool calls.
    • .gitignore is not consulted by the file watcher. There is no respectGitignore setting that applies.
    • There is no built-in filename filter, content scrubber, or user-facing kill-switch for FileChanged content injection.

    Sequence of events

    # Event Result
    1 User pastes a malformed Cloudflare token (curl example) into .dev.vars File is broken
    2 Agent runs grep -n curl .dev.vars to locate the bad line Cloudflare token written to transcript (Exposure #1)
    3 User rotates Cloudflare token, pastes new value, saves file New token in .dev.vars
    4 Harness emits FileChanged system reminder containing lines 15–23 of .dev.vars New Cloudflare token + Qdrant API key written to transcript (Exposure #2)
    5 Agent rewrites PreToolUse guard hook to close grep/awk/sed/.dev.vars gaps Hook tested and live
    6 Agent writes FileChanged blocking hook, wires it into settings.json, tests it Hook tested and live
    7 User instructs agent to file public bug reports and incident document #44868, #44909, this report

    Credentials requiring rotation as a direct result of this session:

    • Cloudflare API token: rotated twice
    • Qdrant API key: rotated once

    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.md rules, .gitignore, model self-monitoring — is advisory or operates at the wrong layer to catch the actual leaks.

    Defense Layer Coverage
    CLAUDE.md rules Model prompt Advisory only; model can and does ignore in-the-moment
    Model self-monitoring Model output Fires after the secret has already been produced
    .gitignore Git tooling Not consulted by Claude Code's file watcher
    PreToolUse hooks Harness, before tool calls Only catches tool-call leaks; does not see FileChanged
    FileChanged hooks Harness, before notifications Exists but undocumented as a security mechanism; users do not know to use it

    There is no default-on, harness-enforced filename filter. There is no default-on output scrubber. There is no setting to disable FileChanged content injection. There is no documentation warning users that editing a .env file 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.

    1. ~/.claude/hooks-guard-secrets.mjs — full rewrite. Blocks cat, head, tail, less, more, type, get-content, gc, select-string, sed, awk, xxd, od, strings, base64, cp, mv, scp, rsync, curl, wget, printf, echo against any path matching sensitive-file patterns. Allows grep/rg only with -c/--count. Allows wc, stat, ls. Exempts .example/.sample/.template suffixes. Verified against 8 reproduction cases.

    2. ~/.claude/hooks-block-filechanged-secrets.mjs — new file. Wired into the FileChanged event in ~/.claude/settings.json. Blocks FileChanged notifications for sensitive-file patterns. Verified against 5 reproduction cases.

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

    4. Repo-level memory — feedback_secrets_handling.md documenting 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

    1. Default-on filename filter for FileChanged content injection. When a modified file matches a sensitive-pattern path (.env*, .dev.vars*, *.pem, *.key, id_rsa*, credentials*.json, service-account*.json, anything containing secret/token/credential), the harness should emit metadata only, not content.

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

    3. Respect .gitignore for the file watcher. Ship a fileChanged.respectGitignore setting defaulting to true. Files ignored by git should not have their contents injected into the conversation transcript without explicit opt-in.

    4. User-facing kill-switch. Add fileChanged.includeContent: false for users who want metadata-only notifications regardless of filename pattern.

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

    6. Notification of affected users. Users whose past Claude Code sessions touched sensitive-pattern files should be notified so they can rotate proactively.

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

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

  2. github-actions commented on May 20, 2026

    @github-actions

    Closing for now — inactive for too long. Please open a new issue if this is still relevant.

  3. github-actions commented on Jul 10, 2026

    @github-actions

    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.

  4. locked as resolved and limited conversation to collaborators on Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions