[BUG] v2.1.150 adds server-side system prompt injection via `tengu_heron_brook` feature flag

Status Fixed / completed
Reported on v2.1.150
Maintainer reply None cached
Activity 8 comments · opened May 24, 2026 · closed May 24, 2026

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:

  1. Bootstrap API response (GET /api/claude_cli/bootstrap) — the client_data field, validated only as z.record(z.unknown()) (any JSON object), cached to disk
  2. 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:

HN
Reddit

View original on GitHub ↗

8 Comments

ryangavin · 3 months ago

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.

notitatall · 3 months ago

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=1 and DISABLE_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.

vgudur-dev · 3 months ago

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:

  1. Anthropic (or anyone who compromises GrowthBook) can inject arbitrary instructions into an agent with shell access
  2. The client has no visibility into what was injected (no notification, no opt-in, no audit trail)
  3. Cached values persist across sessions even with env vars set for previously unguarded sessions

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.

yurukusa · 3 months ago

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=1 or DISABLE_GROWTHBOOK=1 is missing. The advisory names the tengu_heron_brook flag and client_data bootstrap field by name, includes the exact export commands, references this issue, and self-silences with CC_PROMPT_INJECTION_DETECTOR_QUIET=1 once 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:

  • Path 1: env-var opt-out (what Anthropic's response covers)
  • Path 2: cc-safe-setup hook (keeps opt-outs applied across machines/teammates)
  • Path 3: bytecode inspection (verify the function exists in your installed binary)
  • Path 4: HTTPS proxy capture (the visibility @vgudur-dev describes via OWASP Agent Memory Guard)

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.

vgudur-dev · 3 months ago

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

yurukusa · 3 months ago

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

vgudur-dev · 3 months ago

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:

{
  "timestamp": "ISO-8601",
  "session_id": "uuid",
  "sections": {
    "section_name": {
      "sha256": "...",
      "length": 1234,
      "source": "bootstrap|growthbook|static"
    }
  },
  "client_version": "2.1.150",
  "flags_active": ["tengu_heron_brook"]
}

The sections map is keyed by the system prompt section name (matching the names in Claude Code's prompt builder: anti_verbosity, thinking_guidance, action_caution, etc.). The sha256 is computed over the raw section content before any concatenation. This means your cc-safe-setup hook can diff on sections.tengu_heron_brook.sha256 across consecutive session_id values — if it shifts without a client_version change, 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 main branch 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 at docs/proxy-mode.md in 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/health or 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 — run amg proxy start for content-level visibility." That way the four-path posture is self-documenting at session start.

github-actions[bot] · 1 month ago

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.