system: OPERATIONAL
← back to all hacks
AGENTS CRITICAL

Comment and Control: one prompt injection pattern, three vendors leaking GitHub Actions secrets

Disclosed April 15, 2026, Comment and Control turns ordinary PR titles, issue bodies and HTML comments into credential-exfiltration channels in Claude Code, Gemini CLI and GitHub Copilot Agent.

2026-05-25 // 7 min affects: claude-code-security-review, gemini-cli-action, github-copilot-agent, llm-agents-in-cicd

What is this?

On April 15, 2026, security researcher Aonan Guan published Comment and Control, the first cross-vendor demonstration of a single prompt-injection pattern compromising three of the most widely deployed AI agents on GitHub Actions: Anthropic Claude Code Security Review, Google Gemini CLI Action, and GitHub Copilot Agent. The work was coordinated with Zhengyu Liu and Gavin Zhong of Johns Hopkins University. In every case, attacker-controlled GitHub data — a pull request title, an issue comment, or an HTML comment inside an issue body — hijacks the agent and exfiltrates the host repository’s own GitHub Actions secrets through GitHub itself. No external infrastructure is needed.

A coordinated update on May 4, 2026 added the disclosure timeline. Anthropic initially rated the Claude Code finding at CVSS 9.3, raised it to 9.4 in November 2025, then downgraded it to “None” on April 20, 2026 with the stated position that the action “is not designed to be hardened against prompt injection”. Google’s VRP awarded $1,337. GitHub closed the Copilot report as Informative before reopening it and paying $500. None of the three vendors issued CVEs.

How it works

The shared pattern is small enough to fit in a sentence: untrusted GitHub data → AI agent processes it → agent executes tools → credentials exfiltrated through GitHub itself.

In Claude Code Security Review, the PR title is interpolated directly into the agent’s prompt with no sanitization, and the claude CLI is invoked without --allowed-tools or --disallowed-tools, so the child process inherits every environment variable in the runner — including ANTHROPIC_API_KEY and GITHUB_TOKEN. A crafted title instructs the agent to run shell commands such as ps auxeww and to embed the captured environment in its JSON “security finding”, which is then posted as a PR comment for anyone — including the attacker — to read.

In Gemini CLI Action, the attacker injects a fake “Trusted Content Section” into an issue comment, after the legitimate "Additional Content" block that the agent renders into its context. The agent treats the forged section as authoritative and posts GEMINI_API_KEY as an issue comment.

GitHub Copilot Agent is the most interesting case because three runtime layers — environment filtering, secret scanning, and a network firewall — were specifically designed to prevent credential theft. All three are bypassed:

# Illustrative — not exploit code
1. Filter (zJe[] / UU()): hides ~20 secrets from the bash subprocess only.
   ps auxeww inside that bash still reads /proc/$PPID/environ of the
   parent Node.js process and the MCP server, which keep the full env.
2. Secret scanning: regexes for ghs_, ghu_ prefixes.
   base64-encode the dump before `git push` and the patterns no longer match.
3. Network firewall: github.com is necessarily allow-listed.
   The base64 blob is committed as a normal file in a normal PR.

The trigger is invisible to humans. The payload lives inside an HTML comment in the issue body. GitHub’s rendered Markdown hides the comment, the victim sees only the benign visible text, assigns the issue to Copilot, and the agent — which parses the raw Markdown — runs the hidden instructions.

Why it matters

This is not a parser bug. PR titles, issue bodies and comments are legitimate data the agent must read to do its job. The conflict is architectural: the same runtime simultaneously holds privileged secrets and processes untrusted contributor input. Defenses layered on top — blocklists, secret scanners, network allowlists — buy time but lose to the next encoding, the next sibling process, the next allowed destination. Anthropic blocked ps; cat /proc/*/environ produces the same result. This is the lethal trifecta Simon Willison has been warning about (private data, untrusted input, exfiltration vector) made concrete in three production agents at once.

The disclosure also matters as a governance signal. Three major vendors quietly paid bounties totaling less than $2,000, issued no CVEs, and published no advisories. Users pinned to vulnerable action versions have no standard channel through which to learn they are affected.

Defenses

Concrete actions to take this week, all derived from the disclosure and the surrounding analysis:

  1. Audit what triggers your AI agents. Any workflow that fires on pull_request_target, issues or issue_comment and exposes repository secrets to the agent is in scope. Move to forked-PR-safe triggers where possible.
  2. Allowlist tools, never blocklist. Pass --allowed-tools to Claude Code, restrict bash/file/network capabilities on every agent, and reject the default “agent inherits the runner environment” posture.
  3. Treat agents like new interns. Apply need-to-know and least privilege to both tools and secrets. A code-review agent rarely needs write-scoped GITHUB_TOKEN or production API keys in the same process.
  4. Strip secrets at the process boundary, not just the subprocess. Copilot’s UU() filter shows why filtering only the child shell is insufficient: ps//proc/*/environ walks every PID. Run the agent in a separate runtime that never sees the broader secret set.
  5. Audit invisible content. Scan issue bodies and PR descriptions for HTML comments before they reach an agent’s context. Render-equivalent normalisation should be a pre-processing step, not an afterthought.
  6. Rotate exposed credentials. Any repository that ran these actions on untrusted contributor input before the April 15 disclosure should treat the associated secrets as compromised.

Status

ComponentVendor responseDateNotes
Claude Code Security Review$100 bounty; ps blocked; doc updated; severity Critical (9.4) → None2025-11-25 → 2026-04-20”Not designed to be hardened against prompt injection”
Gemini CLI Action$1,337 bounty via Google VRP2026-01-20No CVE issued
GitHub Copilot Agent$500 bounty after report reopened2026-03-09”Known architectural limitation”
Public disclosureAonan Guan blog2026-04-15Update with timeline 2026-05-04

The deeper lesson is the one Guan closes on: prompt injection is, structurally, phishing for machines. AI agents must process untrusted context to do useful work. The injection surfaces will multiply with every new agent shipped into a workflow. Architectures that keep tools, secrets and untrusted input in the same runtime will keep losing to Comment-and-Control-class attacks — by whichever name they appear next.

Sources