Hidden Shell Commands in a Pull Request Title Ran Inside PX4's Build. 83 Items, 61 Repositories, 43 Minutes, and No Repository Access Needed.

On September 1, someone opened 83 issues, pull requests and comments across 61 public GitHub repositories. 77 of them went up in 43 minutes. Each carried a short shell command, and in at least two cases the project's own automation ran it. Radware published the research on October 29. This is what it found, what it did not prove, and what we added to our feed because of it.
The research is Radware's, by Zvika Babo and Gabi Nakibly. We read the original report and checked it against The Register's coverage. We did not find the campaign first, and nothing here says otherwise.
How a title becomes a command
Many projects run a GitHub Actions workflow when someone opens an issue or a pull request: post to Discord, check the title format, add a label. To do that, a workflow often reads the title or body.
The trap is a detail of how GitHub Actions works. An expression that reads an issue title is replaced with its text before the shell ever sees the script. If the title contains the shell's command-substitution syntax, the shell treats it as a command and runs it. Quoting does not help, because the shell still performs substitution inside double quotes.
Nothing is approved, merged or even read by a person. The workflow fires within a second of the event, so a pull request that is closed in minutes has already done its work.
The payload in this campaign was one line. It collected the job's environment variables, encoded them, and sent them by HTTP POST to a server on port 8443, with a unique identifier in the path so the attacker could tell its 83 attempts apart.
What Radware proved
In two cases the evidence is direct, taken from the original build logs.
In PX4-Autopilot, a pull request titled as a documentation typo fix carried the command at the end of its title. The workflow step that checks title format passed the whole title to a Python script inside a shell command. The log shows the shell reporting command substitution, and the script then received binary data that began with a GIF header where the command had been. The script crashed on it.
In Snouzy/workout-cool, a Discord notification workflow assigned the issue title to a shell variable. The log shows the payload on that line and the shell's substitution warning on the same line number.
What it did not prove
Radware could not show that the attacker's server received anything, that any secret was exposed, or that anyone gained write access to a repository. The supply-chain consequences it describes are what could follow, not what was verified. Most of the affected repositories have since been closed or fixed, and Radware cannot confirm that all have been.
The numbers
- 83 verified malicious items across 61 repositories, all on September 1.
- Two waves: 77 items between 11:26 and 12:10 UTC, then 6 more between 18:17 and 18:19 UTC.
- 59 issues and 24 pull requests. The command sat in the title 31 times, in the body 17 times and in a comment 35 times, so a workflow that reads only comments was exposed even when the title looked clean.
- 74 distinct accounts, plus two now shown as deleted, all pointing at one collection server.
- Targets included projects with tens of thousands of stars: agno, ToolJet, langextract, trivy, CopilotKit, podman, qwen-code, mlflow, discord.js, cilium, brave-browser and github/docs.

The accounts are not the indicator
Some of the 74 accounts had real development histories before September 1. In one repository the owner of an account wrote, under an issue attributed to it, that they had never opened it and asked if they had been hacked. Radware's hypothesis, and ours, is that some of these are people whose accounts were taken over, not the attackers themselves. We did not put any account name in our feed. A person is not a blockable indicator, and listing a victim would punish them twice.
The AI agent angle
One maintainer told Radware that their workflows had not run because of existing restrictions, but that an AI-powered comment-review agent could have processed the attacker's text while holding sensitive credentials. That is the same boundary, untrusted text flowing into something privileged, with a model in the middle. If an agent reads your issues and can also reach your secrets, it needs the same treatment as a workflow: least privilege, and separation between what reads outside text and what holds keys.
What we added
The collection server is 54.205.16.142, on port 8443, with a path that starts with /c/ followed by a unique identifier. It was not in our corpus. We added it to the feed: the address, plus the three full collection URLs Radware published, at confidence 85, credited to Radware and marked as not our discovery. The address is an Amazon EC2 host in us-east-1, so do not block its surrounding range. We held the confidence below our top tier because we have not independently seen the server.
What to check in your own repositories
- Search workflow run logs from September 1 for connections to 54.205.16.142, especially on port 8443.
- List every workflow triggered by an issue, pull request or comment event. For each one, look for event text such as a title, body or comment pasted straight into a run step.
- Pass event text through an environment variable and read it as data inside the script. Never let it be built into the script itself, and never pass it to an evaluation command afterward.
- Give those jobs the least access possible, and keep them separate from any job that holds deployment keys or release permissions.
- If you find a hit, work out which secrets that job could see, rotate them, and read the audit logs.
We checked ours. Our private repository has no workflow triggered by issue or comment events, and none that uses the pull-request-target trigger, which is the one that runs with secrets on outside content. The one place we pass outside text into a script, a commit message, already goes through an environment variable, because an unquoted backtick in a message ran as a command in our own build on October 14.
Limits
Everything here comes from Radware's report and The Register's account of it; we did not reproduce any of it. The two evidence cases are the only confirmed executions. Our confidence in the indicators is 85, not higher, because a single cloud address can change hands. The timing is Radware's.
Five percent of this is wrong somewhere. It always is.
If this was useful, or was not, rate it at the bottom of the page.
How do AI models see YOUR brand?
AIPM has audited 250+ domains. 15 seconds. Free while still in beta.
Audit your site now → aipmsec.com
Was this useful? Thirty seconds, no cookies, no tracking, no third parties, your address hashed and never stored. The question lives here: https://analytics.dugganusa.com/nps.html#post=hidden-shell-commands-in-a-pull-request-title-ran-inside-px4-s-build-83-items-61-repositories-43