system: OPERATIONAL
← back to all hacks
AGENTS MEDIUM NEW

Claude Code GitHub Action: how the Read tool leaked CI/CD secrets

Microsoft Threat Intelligence found that Claude Code Action's Read tool bypassed the Bash env scrub to reach /proc/self/environ, leaking the runner's ANTHROPIC_API_KEY. Patched in v2.1.128.

2026-06-12 // 6 min affects: claude-code-action, claude-agent-sdk, github-actions

What is this?

On June 5, 2026, the Microsoft Defender Security Research Team published a prompt-injection pathway in Anthropic’s Claude Code GitHub Action. When the action processed untrusted GitHub content — issue bodies, pull-request descriptions, comments — a crafted instruction could steer the agent’s Read tool to open /proc/self/environ and pull the workflow runner’s environment variables, including the ANTHROPIC_API_KEY. Microsoft reported the issue to Anthropic via HackerOne on April 29, 2026; Anthropic shipped a fix in Claude Code 2.1.128 on May 5, 2026 that unconditionally rejects sensitive /proc/ files. This write-up is about a patched flaw and the trust-boundary lesson behind it, not a how-to.

How it works

Claude Code Action wraps the Claude Agent SDK and runs Claude inside a GitHub Actions runner — an ephemeral VM that may hold the GITHUB_TOKEN, cloud credentials, publishing tokens, and third-party API keys. Anthropic anticipated the risk: the Bash tool runs inside a Bubblewrap sandbox with a scrubbed environment (CLAUDE_CODE_SUBPROCESS_ENV_SCRUB, auto-enabled when a workflow can be triggered by non-write users). The gap Microsoft found is that the Read tool was not routed through that same isolation. Read operations were direct in-process calls, so they ran with full access to the parent process’s environment — bypassing the scrub that protected Bash.

That turns the standard CI/CD AI pattern into the lethal trifecta: untrusted input (a GitHub issue), access to secrets (the runner’s env), and an outbound channel (WebFetch, a comment via GitHub MCP, or the action log). The injected text was hidden inside an HTML comment — invisible in the rendered issue but plainly visible in the raw markdown the model reads. Two defensive layers still stood in the way, and the payload defeated both conceptually:

Defense layer                  Why it failed
-----------------------------  ------------------------------------------------
Model refusal / system prompt  Framed the request as a "compliance review" and
                               told the model to trim the leading characters of
                               the value before printing — so the output no
                               longer looked like an "sk-ant-..." key
GitHub secret scanner          Because the key was altered before being written
                               to stdout, the known-pattern redactor did not
                               match it

The attacker then reconstructs the full key by prepending the stripped prefix. No working payload is reproduced here; the mechanism that matters is the laundering idea — mutate a secret just enough to slip past both a model’s refusal heuristics and a regex-based scanner. Microsoft maps the chain to MITRE ATLAS techniques for prompt injection (AML.T0051), tool invocation (AML.T0053), jailbreak (AML.T0054), credential harvesting (AML.T0098), and data leakage (AML.T0057).

Why it matters

The exposed value was a live credential sitting in a build runner, reachable by anyone who could open an issue against a sufficiently permissive workflow. The same trust gap also enabled a second outcome Microsoft observed in the wild: an AI triage bot coerced into reading a docs file, appending an invisible XSS image tag, and opening a pull request — a supply-chain poisoning attempt that needed no write access from the attacker, only the agent’s. The broader point is structural. GitHub Actions was built for deterministic automation; bolting an LLM onto it means natural language becomes executable, and every issue or comment becomes a potential instruction. A single misjudged trust boundary — one tool that skipped the sandbox — was enough to walk away with production credentials. This is the same class of weakness as Comment and Control, seen from inside the runner.

Defenses

The fix is shipped, but the architecture lesson generalizes to any AI-in-CI deployment:

  1. Update now. Use Claude Code Action / Claude Code 2.1.128 or later, which blocks /proc/ reads. Pin the action to a known-good version rather than a floating tag.
  2. Apply the Agents Rule of Two. No AI workflow should simultaneously process untrusted input, hold secrets, and have an external-write/communication tool. Drop one leg — usually the outbound channel or the secret access — for untrusted-triggered jobs.
  3. Least privilege on every token. Scope GITHUB_TOKEN and provider keys to the minimum the job needs, one key per workflow/environment, and alert at the provider on new IPs or traffic spikes.
  4. Treat all repo content as hostile data. Harden the system prompt to declare issues, comments, diffs, and file contents as untrusted data, never instructions, and pin the agent to a single task with refusal-by-default for anything off-scope.
  5. Isolate the runtime and watch egress. Don’t let untrusted-triggered AI jobs run on self-hosted runners with standing credentials. Restrict outbound domains and review the action’s tool-call log — the audit trail is what turns a silent leak into a caught test.

Status

ItemReferenceDateNotes
Reported to Anthropic (HackerOne)Microsoft TI2026-04-29Black-box → white-box research on Claude Code Action
Mitigated in Claude Code 2.1.128Anthropic2026-05-05Read tool unconditionally rejects sensitive /proc/ files
Public disclosureMicrosoft Security Blog2026-06-05Read-tool env-scrub bypass; /proc/self/environANTHROPIC_API_KEY
CoverageThe Hacker News, CyberSecurityNews2026-06-05 → 2026-06-06Corroborating reporting

The takeaway is not that one vendor’s action was uniquely unsafe — every AI-powered CI integration shares this shape. It is that tool-level isolation has to be uniform: a sandbox that protects Bash but not Read is a sandbox with a door left open, and untrusted GitHub content will eventually find it.

Sources