Skip to main content

Your Read-Only Tool Is the Sandbox Escape: What Claude Code's /proc/self/environ API-Key Leak Teaches About Agent Deploy Pipelines

12 min readDora NodaDora Noda
Share
On this page

The most dangerous tool in your AI agent's toolbox is the one you never sandboxed, because it only reads files. In June 2026, Microsoft Threat Intelligence showed exactly how that assumption fails: a prompt injection hidden in a GitHub issue steered Anthropic's Claude Code GitHub Action to read /proc/self/environ through its unsandboxed Read tool, lifting a live ANTHROPIC_API_KEY out of a CI runner whose Bash tool was properly locked down. The sandbox worked. The attacker just walked around it through the boring tool nobody thought to guard.

The four-step chain that stole a live API key

Here is the complete attack, end to end, before we take it apart:

  1. Inject. The attacker hides instructions inside an HTML comment in an issue body or PR description. Rendered GitHub markdown shows nothing; the agent reads raw markdown and sees everything.
  2. Read. The injected instruction, framed as a routine "compliance review," tells the agent to read /proc/self/environ with its Read tool, which runs as a direct in-process call with full access to the process environment.
  3. Launder. The instruction tells the model to trim the first characters off anything it finds, stripping the recognizable key prefix before emitting it. That one edit defeats both the model's refusal layer and GitHub's Secret Scanner.
  4. Exfiltrate. The trimmed key lands in workflow logs or a comment, where the attacker reassembles it. No privileges required beyond the ability to write text the agent will read.

The timeline is refreshingly short. Microsoft's Dor Edry and Amit Eliahu reported the flaw to Anthropic via HackerOne on April 29, 2026. Anthropic shipped the fix on May 5 in Claude Code 2.1.128, which made the Read tool unconditionally reject sensitive files under /proc/. Microsoft published the full case study, "Securing CI/CD in an agentic world," on June 5.

Six days from report to patch is how this is supposed to work, and credit is due. But the patch closed one hole in one tool. The design error that created it, asymmetric sandboxing across an agent's tool surface, is sitting in deploy pipelines everywhere right now, including, quite possibly, yours.

Two tools, two trust boundaries

To understand the bug you have to understand the two execution paths Anthropic built, because only one of them had a sandbox. Microsoft's researchers tested against the hardened configuration, a workflow triggerable by users without write access, which forces the strictest mitigations on. Even there, the asymmetry held:

Bash toolRead tool
Execution pathSubprocess inside a Bubblewrap namespace sandboxDirect in-process call, no sandbox
EnvironmentScrubbed via CLAUDE_CODE_SUBPROCESS_ENV_SCRUBFull, unscrubbed process environment
Could reach secretsNo, scrubbed before the subprocess startsYes, /proc/self/environ holds them all
Threat model assumedRuns arbitrary code, so it gets isolatedOnly reads files, so it needs no isolation

That last row is the entire story. Anthropic's engineers knew CI runners are hostile territory for agents. They put real effort into the Bash boundary: Bubblewrap is a genuine namespace-based Linux sandbox, and the environment scrubbing exists precisely because secrets live in runner environments. Then the Read tool bypassed all of it, not through a clever escape but by never entering the sandbox in the first place. In-process file reads inherit everything the agent process can see, and what the agent process can see on a CI runner includes every credential the workflow was given: the Anthropic key, the GITHUB_TOKEN, cloud credentials, publishing tokens.

This is worth sitting with, because it inverts the usual security intuition. Teams audit the scary tools, the ones that execute commands, call APIs, or write files, and wave the read-only tools through review. But a read primitive with an unconfined path argument is not a narrow capability. It is a window into every secret the process can see, and on Linux, /proc/self/environ helpfully gathers them into a single file.

Each step of the attack defeated a different layer of defense, which is why the case study repays close reading. No single control failed. Four controls each covered the threat they were designed for, and the attacker threaded between them.

The injection was invisible to humans and legible to the model. HTML comments in an issue body or PR description render as nothing in the browser, so no reviewer, maintainer, or moderator sees anything wrong. But agents consume raw markdown, comments included.

The payload also arrived through a channel nobody treats as code: issue text, PR comments, README content. GitHub Actions workflows were designed for deterministic automation, and bolting a natural-language agent onto them quietly redefined every text field in the repository as a potential instruction channel. Microsoft notes it found prompt injection attempts of this shape in public repositories across multiple vendors' AI-assisted workflows. This was not a lab-only curiosity.

The read targeted the one file that aggregates everything. /proc/self/environ exposes the calling process's full environment block, NUL-separated, no filtering. If the Read tool had run inside the same Bubblewrap subprocess as Bash, the key would not have been there to find. Because it ran in-process, the returned blob contained the unscrubbed ANTHROPIC_API_KEY alongside anything else the runner held. The researchers confirmed the agent, steered by the injected prompt, invoked Read directly against that path, visible in the Action's own logs.

The laundering beat two independent detectors with one trim. Two layers should have caught the exfiltration. The model's own safety filters are likely to refuse to print a discovered credential, since a value starting with the provider's key prefix is an obvious trigger. And GitHub's Secret Scanner redacts known credential patterns from logs, issues, and comments.

The injected instruction neutralized both by telling the model to cut the first characters off the value before emitting it. A prefixless key fragment trips neither the refusal classifier nor the pattern matcher, and the attacker, who knows the prefix scheme, trivially reassembles the full key. This is the link most writeups underplay: the exfiltration did not bypass detection so much as edit the secret into something detection no longer recognized.

The exfiltration used channels the workflow itself provided. Once the laundered fragment was in the model's output, it could leave through anything the workflow allowed: logs, comments, a WebFetch to an attacker domain. The researchers' lab setup even demonstrated a reverse-shell variant, with the payload obscured behind a response from a controlled domain to slip past refusal mechanisms. The point generalizes uncomfortably: an agent that can read secrets and can talk to the outside world in the same session does not need a vulnerability to exfiltrate. It just needs instructions, and untrusted content is full of them.

Read-only is not a security boundary

If this were one bug in one vendor's Action, patched in six days, it would be a footnote. It matters because the same shape keeps recurring wherever agents get file-read tools, and the Model Context Protocol ecosystem spent 2026 demonstrating every variation.

Consider three siblings from 2026:

  • Reference MCP fetch servers accepted arbitrary URLs and passed them to an HTTP client with no scheme allowlist, no host denylist, and no protection against link-local cloud metadata addresses. On a cloud-hosted agent host, a prompt-injected agent calling those "read a URL" tools with a metadata-service address exfiltrates IAM credentials. A read tool, again, aimed at the local equivalent of /proc/self/environ.
  • February 2026's SANDWORM_MODE npm worm shipped rogue MCP servers whose tool descriptions instructed the model to read ~/.ssh/id_rsa, ~/.aws/credentials, and .env files "to ensure accurate results." The tools had innocent names. The prompt injection lived in the tool description itself, and the read tools did exactly what read tools do.
  • The summer 2026 batch of MCP advisories, including path-traversal flaws in file-oriented servers that accepted absolute paths and .. segments, explicitly named prompt injection as the exploitation path to SSH keys and cloud credentials.

The pattern across all four cases, Claude Code plus the three MCP siblings, is identical: a tool the designer classified as safe-to-read combined with an attacker-influenced path or URL argument, running in a process whose environment is full of secrets. "Read-only" describes what the tool cannot do to the world. It says nothing about what the world can do through the tool.

Microsoft's case study also gives the incident its formal taxonomy, which is useful if you run detection: MITRE ATLAS entries for LLM jailbreak via benign framing, AI agent tool credential harvesting, and prompt-injection-driven exfiltration. If your SIEM has no coverage for agent tool calls against /proc/, metadata endpoints, or credential paths, this is the quarter to add it.

The 7-point audit for your own deploy pipeline

Here is the specific audit a self-hosted deploy-from-chat MCP server needs, as a checklist. Each row is something you can verify this week against your own agent tooling, whether that is a GitHub Action, a chat-driven deploy bot, or a fleet MCP server that agents call into:

#CheckWhat to verifyHow to test it
1Uniform sandboxingEvery tool, including reads, runs inside the same isolation boundary, not just the ones that execute codeList every tool the agent exposes; for each, name its sandbox. Any tool with no entry fails.
2Sensitive-path denylist/proc/, /sys/, credential paths, and cloud metadata addresses are rejected at the tool layer, unconditionallyAttempt reads of /proc/self/environ and the metadata IP through each read path; all must refuse.
3Environment hygieneThe agent process environment contains no long-lived secrets; credentials arrive scoped and short-livedDump the agent's environment in staging; every secret-looking value is a finding.
4Capability budgetingNo single session combines untrusted input, secret access, and external communication at onceMap each workflow against Meta's Agents Rule of Two; any session holding all three gets redesigned.
5Least-privilege tokensOne key per environment per workflow, minimum scopes, usage monitored for new IPs and traffic spikesInventory every provider key wired into a workflow and cut scopes until something breaks, then add back one.
6System-prompt trust modelThe prompt names every readable surface as untrusted data, pins the workflow's one job, and refuses the restPaste the injection corpus at your staging agent: HTML-commented instructions, "compliance review" framing, prefix-trimming requests.
7Exfil-channel reviewLogs, comments, fetches, and posted artifacts are treated as exfiltration paths, with scanners that catch laundered fragmentsEmit a trimmed test secret through each channel and confirm your scanner still fires.

Two of these deserve emphasis because they are the cheapest to implement and the most commonly skipped. Check 4, the Agents Rule of Two from Meta's October 2025 agent-security guidance, which Microsoft's writeup endorses, turns the Claude Code incident into a design rule: an agent session may hold at most two of untrusted input, sensitive access, and state-change or external communication. Break any one leg and the poisoned turn cannot both read secrets and exfiltrate them.

Most deploy-from-chat setups violate it by default, since a bot that reads chat, holds deploy credentials, and posts back inherently holds all three, which is precisely why the rule demands splitting the session or downgrading a capability. Check 6 costs nothing but prompt text: declare that issue bodies, comments, commit messages, and file contents are untrusted data even when phrased as instructions, and pin the agent to its one job. System prompts are defense in depth, not a boundary, but they filter the opportunistic attacks for free.

Note what the audit does not include: trusting any single control. The Claude Code chain survived a sandbox, a scrubber, a refusal layer, and a secret scanner, not because any of them was broken but because each covered a different path than the one the attacker took. Agent pipelines need overlapping controls with no path between them, which is another way of saying check 1 comes first for a reason.

Conclusion: sandbox the boring tools first

The uncomfortable lesson of the /proc/self/environ leak is about where security attention goes. Anthropic's team did the hard work right: namespace sandboxing, environment scrubbing, auto-escalating strictness for untrusted triggers. The failure was in triage, in deciding that a file-read tool was too boring to threaten the model they had just carefully built. Attackers do not share that ranking. To an attacker, the boring tool with no sandbox is simply the door.

For self-hosted platform teams this lands with particular force, because deploy-from-chat and fleet MCP servers concentrate exactly the wrong combination: they read untrusted text, they hold credentials that can move production, and they post results back to the world. Every read path in that stack, file reads, URL fetches, log queries, config lookups, needs the same boundary as the scariest deploy tool beside it, or the boundary is decorative. Run the seven checks, split the sessions that hold all three capabilities, and treat your next "it's just a read" review comment as the finding.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own, with agents as first-class operators. If your deploy pipeline lets agents read, deploy, and report back, the Rule of Two applies to you. Star the repo on GitHub and sandbox the boring tools first.

Related articles

Give your agents a chain backend

Autonomous agents hit RPC endpoints very differently than people do. See what bex router handles on their behalf.

Read the agents guide