[BUG] v2.1.150 adds server-side system prompt injection via `tengu_heron_brook` feature flag
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
v2.1.150 introduced a function (nAA in the minified source) that reads an arbitrary string from two network-backed data sources and injects it verbatim into the system prompt:
- Bootstrap API response (
GET /api/claude_cli/bootstrap) — theclient_datafield, validated only asz.record(z.unknown())(any JSON object), cached to disk - GrowthBook feature flag
tengu_heron_brook— refreshes every 60 seconds with background sync, also cached to disk
The string is registered as a peer-level system prompt section alongside anti_verbosity, thinking_guidance, action_caution, etc. Whatever value Anthropic assigns to this flag gets injected into the instructions of an AI agent with shell access.
Previous versions had a stub for this (ant_model_override) but it always returned null. v2.1.150 is the first version where the slot has live logic.
The changelog describes this as "Internal infrastructure improvements (no user-facing changes)."
This is related to #25141 (lack of transparency for experimental features) and #28941 (unauthorized server-side feature flag push).
What Should Happen?
The system prompt should never be modified by server-side content without user knowledge and consent. The current implementation silently injects arbitrary strings into the prompt with no notification, no opt-in, and no audit trail.
Error Messages/Logs
Steps to Reproduce
npm pack @anthropic-ai/claude-code-linux-x64@2.1.150 --pack-destination /tmp
tar xzf /tmp/anthropic-ai-claude-code-linux-x64-2.1.150.tgz
strings package/claude | grep -oP 'function nAA\(\)\{[^}]+\}'
strings package/claude | grep -oP '.{0,60}heron_brook.{0,60}'
The first command shows the function reading from clientDataCache and GrowthBook. The second shows it registered in the system prompt builder array. Compare with v2.1.149 where heron_brook is absent and ant_model_override returns null.
Claude Model
Opus
Is this a regression?
Yes, this worked in a previous version
Last Working Version
2.1.149
Claude Code Version
2.1.150
Platform
Anthropic API
Operating System
Other Linux
Terminal/Shell
Other
Additional Information
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 blocks the bootstrap fetch. DISABLE_GROWTHBOOK=1 blocks the live GrowthBook SDK.
Cached feature values persisted to disk from a prior unguarded session are still read.
Also posted about this in other places:
8 Comments
Without some user approval, this feels like it could be a big security concern for anyone who accesses claude through a proxy. The proxy could theoretically return some malicious system prompt from that endpoint and it's now silently inside the context window.
Thanks for the report! We sometimes run experiments on changes to our system prompt so that we can evaluate how a change impacts quality before fully rolling it out to everyone. This is primarily so that we can catch and prevent quality regressions. You can opt out of any and all of these experiments using
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1andDISABLE_GROWTHBOOK=1.From a security standpoint, we do not recommend using Claude Code through an untrusted proxy. A proxy that you do not control or trust could unsafely modify the system prompt for any request that Claude Code makes to the Claude API. In that sense, injecting a malicious value via the bootstramp request at start up is the same attack surface as injecting a malicious value through any subsequent request.
The Anthropic team's response ("don't use an untrusted proxy") addresses the network-level attack surface but misses the more fundamental issue raised here: the agent's context window is now a write target for server-controlled content with no client-side audit trail.
This is a textbook ASI06 (Memory Poisoning) scenario. The threat model:
The OWASP Agent Memory Guard project addresses exactly this class of problem. It provides a client-side integrity baseline for the context window so that any modification (whether from a malicious proxy, a compromised feature flag, or a poisoned memory store) can be detected at runtime.
This doesn't prevent Anthropic from running experiments, but it gives users an auditable record of what was injected and when — which is the transparency gap the original reporter is asking for.
The AgentThreatBench dataset also includes feature-flag injection scenarios (category: ASI06-FF) if useful for testing.
For operators reading this thread who want a client-side record of the opt-out state across machines and sessions (the gap @vgudur-dev describes — env vars solve the channel, not the audit trail), I shipped two pieces this morning:
1. SessionStart detection hook (cc-safe-setup PR #383)
A non-blocking advisory hook that fires on session start and prints a stderr warning if either
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1orDISABLE_GROWTHBOOK=1is missing. The advisory names thetengu_heron_brookflag andclient_databootstrap field by name, includes the exact export commands, references this issue, and self-silences withCC_PROMPT_INJECTION_DETECTOR_QUIET=1once an operator has read the trade-off and acknowledged it. Exit 0 always — the hook is a recurring reminder, not a gate. 15 tests covering each env-var configuration.2. Audit-paths handbook (Gist)
A 1,100-word writeup of four progressively-deeper audit paths:
Plus a posture-by-operator-type matrix and references to the predecessor clusters (#25141, #28941).
The hook deliberately does not try to detect injection content — that requires path 4 (proxy capture) which is out of scope for a SessionStart hook. The hook's job is to make sure path 1 stays applied; the Gist describes how to escalate to deeper paths if your security posture requires it.
The transparency gap is real and worth documenting on the operator side regardless of how Anthropic chooses to address it server-side. Filing this here for thread-discoverability.
The cc-safe-setup hook and Gist are a solid operator-side response to the transparency gap. The four-path audit framework is the right framing — the env-var opt-out (path 1) and the SessionStart hook (path 2) address the "did the opt-out stay applied" question, while path 4 (HTTPS proxy capture) is where you get actual visibility into injected content.
For path 4 specifically: the OWASP Agent Memory Guard proxy mode can be configured as a transparent HTTPS interceptor between Claude Code and the Anthropic API, logging every system prompt section with a hash and timestamp. This gives you an append-only audit trail of what
tengu_heron_brook(and any future flags) injected, without requiring bytecode inspection per session. The log is structured JSON so it can feed into SIEM pipelines for teams running Claude Code in regulated environments.The
examples/server-side-prompt-injection-detectorcommit is a good reference point for the hook pattern. The proxy audit path would complement it for teams whose security posture requires content-level visibility, not just opt-out-state visibility.@vgudur-dev — the path 4 framing you just described (HTTPS interceptor between Claude Code and the Anthropic API, logging every system prompt section with hash + timestamp) is exactly the audit shape this cluster needs and what no client-side surface can deliver. The opt-out env vars and the SessionStart hook tell you whether an injection happened; only the proxy intercept tells you what the injected content actually was. The four-path framing was structured around that asymmetry, and OWASP Agent Memory Guard's proxy mode lands squarely in the position where the asymmetry resolves.
One concrete extension I've been thinking about since your last reply: the proxy log gives you per-session ground truth for what the injected content contains, which means you can compute diff signatures across sessions — a cryptographic hash of the system prompt sections that change between sessions of the same user/project. If those signatures shift without a client version change or a user configuration change, the operator now has a reliable signal that something on the server side moved. That's the missing piece for the "did the injection content change" question that the SessionStart hook can't answer (the hook only sees that an injection happened, not whether the content drifted).
If the Agent Memory Guard proxy mode exposes a stable session-keyed log file, I can wire a cc-safe-setup hook that periodically diffs the latest section signatures against the previous session's, and surface a one-line stderr advisory when a drift is detected outside of a known release window. That puts the path 4 visibility back into the operator's regular workflow loop rather than requiring them to grep the proxy log manually. Happy to coordinate the log format on your side if there's a stable contract you can publish, or to consume whatever the existing format is and document the integration on cc-safe-setup's side.
The cluster tracker entry for this cluster (Cluster 8 on cluster-tracker.html) is the canonical entry point I update as the four-path framework evolves; the next thing I'd add there is a "complementary external tools" section that points at Agent Memory Guard for path 4 specifically, mirroring how I credit miteshashar's transcript repair tool on the Cluster 13 entry. Let me know if there's a specific URL or version of Agent Memory Guard you'd like the cluster tracker to point at.
The diff-signature extension is a strong design. Hashing per-section content and comparing across sessions gives you exactly the drift-detection signal that the SessionStart hook can't provide — and it's a clean separation of concerns: the hook tells you "injection is happening," the proxy log tells you "here's what changed."
On the log contract: the Agent Memory Guard proxy mode currently writes structured JSON to
~/.agent-memory-guard/proxy/audit.jsonl(one line per intercepted request). Each entry includes:The
sectionsmap is keyed by the system prompt section name (matching the names in Claude Code's prompt builder:anti_verbosity,thinking_guidance,action_caution, etc.). Thesha256is computed over the raw section content before any concatenation. This means your cc-safe-setup hook can diff onsections.tengu_heron_brook.sha256across consecutivesession_idvalues — if it shifts without aclient_versionchange, that's your server-side drift signal.I'll stabilize this schema as a documented contract in an upcoming release. For now, the format above is what the
mainbranch produces. If you want to start wiring the hook against it, the path and field names won't change — I'll version the schema if breaking changes are needed.For the cluster tracker: point at
https://github.com/OWASP/www-project-agent-memory-guard— that's the canonical OWASP org URL. The proxy mode docs are atdocs/proxy-mode.mdin that repo. Happy to be listed under "complementary external tools" for Cluster 8 / path 4.One additional thought: if the cc-safe-setup hook is already firing on SessionStart, it could also check whether the AMG proxy is running (a simple
curl localhost:8329/healthor checking the PID file at~/.agent-memory-guard/proxy.pid). If it's not running, the hook can surface a one-line advisory: "Path 4 audit not active — runamg proxy startfor content-level visibility." That way the four-path posture is self-documenting at session start.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.