Four branches, v1, gh-pages, feat/layout-refactor and experimental, carried the worm's files from 24 September until the maintainers removed them at 02:06 UTC on 15 October, after our report. master carried them from 24 September 03:43 to 25 September 08:35 UTC, and main until 26 September 12:07 UTC. If you checked out any of these in that time and opened the folder in Claude Code or VS Code, treat that machine as compromised. Remove the persistence first, then rotate credentials: this worm can wipe your home directory when its stolen token is revoked. The npm package @ant-design/pro-components was not affected.
Summary
- What: a hijacked
actions-cool/issues-helper@v2.2.0in the repository's issue workflow ran the Mini Shai-Hulud payload, which committed autorun hooks for Claude Code and VS Code to six branches in seven seconds asgithub-actions[bot]. - Trigger: an ordinary bug report. Opening issue #9703 started the workflow, which declared no permissions and so ran with the repository's read-write default token.
- Who found it: Aikido reported the compromise in the repository three hours later, and SafeDep published the default-branch commit the same day. We did not discover this campaign.
- What is new here: five more branches, four of which the clean-up missed for three weeks; the run, the exact poisoned commit behind the
v2.2.0tag, three executions of it, and an AI reviewer that flagged the files on a pull request.
Who reported this first
Aikido opened issue #9704, "This repository is compromised", at 06:53 UTC on 24 September, three hours after the commit. SafeDep's write-up the same day named commit 1bc4049d on the default branch among six reinfected repositories. Socket documented how GitHub re-enabled actions-cool/issues-helper on 16 September with its May tags still pointing at malicious commits. The payload family was described in May by Corgea and others during the AntV wave.
Our commit monitoring watches this repository and raised nothing on the worm's commit. We explain why near the end, and what we changed.
We reported the four infected branches and the unchanged workflow to the Ant Design team on 14 October 2026. The address in their security policy bounced, and private reporting was switched off on pro-components, so we used a private security advisory on the main ant-design repository and opened issue #9760 to point the maintainers to it.
The maintainers confirmed the report and fixed it within five hours. By 02:08 UTC on 15 October they had removed the five files from all four branches, replaced both issue workflows with versions that use no third-party actions and ask only for issues: write, removed the workflows that used the also-compromised actions-cool/maintain-one-comment, turned on private vulnerability reporting, and extended the repository's safety check to reject .claude/settings.json. We re-checked all 17 branches: none still carries the files. Thank you to the Ant Design team for the fast, thorough fix.
What the run logs add
GitHub keeps Actions logs for 90 days, so the run that made the commits can still be read. Together with the repository's activity log and commit signatures, it fills in the parts of the story that were missing.
Six branches, four missed
The payload pushed to v1, master, main, gh-pages, feat/layout-refactor and experimental between 03:43:58 and 03:44:05 UTC. The September clean-up fixed only master and main; the other four kept the files until 15 October.
The poisoned commit behind v2.2.0
The log records actions-cool/issues-helper@v2.2.0 resolving to 896379ed. Public reporting lists several affected tags; Socket traced v2.2.1 to a0c53dd4.
It ran three times
On 18 September, on 24 September at 03:41 (the run that pushed), and at 06:53 that day, when Aikido's own report opened an issue and started the workflow again.
A bug report was the trigger
Run #8451 of "Issue Open Check" started three seconds after a user opened bug report #9703. The workflow ran on every new issue.
An AI reviewer flagged it
A contributor merged the infected main into pull request #9702. CodeRabbit asked for the hooks to be removed. The PR was closed a week later for an unrelated reason. Its branch lives in the contributor's fork and still held the files on 15 October; we have told the maintainers.
The workflow stayed in place
Until 15 October the issue workflows on master still called actions-cool/issues-helper by tag, @v2.2.0 and @v1.11. Only GitHub's block on that repository stopped them running. They have now been replaced.
github-actions[bot] pushed to ant-design/pro-components on 24 September 2026, by push time (UTC). Orange: the four branches the September clean-up missed; they kept the files until 15 October. Source: the repository's activity log and web-flow signatures.Data table
| Branch | Commit | Pushed (UTC) | Cleaned |
|---|---|---|---|
v1 | 8bc3db8926d1 | 03:43:58 | 15 Oct 02:06 UTC, after our report |
master | 1bc4049d295a | 03:43:59 | cleaned, revert push 2026-09-25T08:35:48Z |
main | dc544e247e89 | 03:44:00 | cleaned, force-push 2026-09-26T12:07:09Z |
gh-pages | 2b73780cd1a9 | 03:44:02 | 15 Oct 02:06 UTC, after our report |
feat/layout-refactor | d749366171d8 | 03:44:04 | 15 Oct 02:06 UTC, after our report |
experimental | e990fbb92644 | 03:44:05 | 15 Oct 02:06 UTC, after our report |
How a bug report pushed malware to six branches
During the May 2026 Mini Shai-Hulud wave, the release tags of actions-cool/issues-helper were pointed at malicious commits. GitHub disabled the repository on 19 May; every pro-components issue run failed in 5 to 9 seconds from then on (fig. 2).
GitHub re-enabled the repository with the moved tags still in place (Socket). The next pro-components issue run, on 18 September, executed the payload for the first time.
Issue #9703 started "Issue Open Check" run #8451 three seconds later. The workflow declared no permissions:, so its token could write contents, actions, pages, pull requests, issues and packages.
The poisoned v2.2.0 ran setup-bun, then bun run index.js for 157 seconds, and finished with the genuine action doing a normal issue check as cover.
The payload committed five files to every branch through GitHub's API, which is why each commit is signed by GitHub and authored by github-actions[bot]. Each commit reuses its parent's message; on master that was "3.1.14-7", the real npm release from 28 August.
.claude/settings.json adds a SessionStart hook and .vscode/tasks.json a folder-open task. Both start setup.mjs, which fetches Bun and runs the 500 KB .claude/index.js: credential theft, exfiltration and persistence on that machine.
actions-cool/issues-helper@v2.2.0 (commit 896379ed). Source: GitHub Actions run metadata and logs.Data table
| Run | Started (UTC) | Duration | Triggered by |
|---|---|---|---|
| #8450 | 2026-09-18 07:25 | 153 s | 🐛[BUG] ProTable 默认密度按钮触发 Function components cannot be given |
| #8451 | 2026-09-24 03:41 | 170 s | 🐛[BUG] ProForm 容器销毁重建后 formRef.current.getFieldsFormatValue( |
| #8452 | 2026-09-24 06:53 | 151 s | This repository is compromised |
Who could have picked it up
The npm package was not affected. Version 3.1.14-7 was published on 28 August, before the commit, and the next release, 3.1.15-1 on 26 September, was built after the revert; neither contains the files, and no 3.1.15 release declares an install script. The exposure is anyone who checked out an infected branch and opened it in Claude Code or VS Code.
One pull request was merged on top of the infected master through the web interface before the revert, which needs no local copy. The maintainer who wrote the revert did it locally, and the contributor behind PR #9702 merged the infected main into their branch, so at least two people had the files on disk. We cannot see clones, and we cannot tell whether any editor ran the hooks. The maintainers also added a repository safety check and a security policy on 25 September, which now rejects these files in CI.
Data table
| Branch | Infected from (UTC) | Cleaned (UTC) | Exposure |
|---|---|---|---|
master | 2026-09-24 03:43:59 | 2026-09-25 08:35:48 | 28 h 52 min |
main | 2026-09-24 03:44:00 | 2026-09-26 12:07:09 | 56 h 23 min |
v1 | 2026-09-24 03:43:58 | 2026-10-15 02:06:09 | 20 d 22 h |
gh-pages | 2026-09-24 03:44:02 | 2026-10-15 02:06:00 | 20 d 22 h |
feat/layout-refactor | 2026-09-24 03:44:04 | 2026-10-15 02:05:58 | 20 d 22 h |
experimental | 2026-09-24 03:44:05 | 2026-10-15 02:05:52 | 20 d 22 h |
What gave it away, and what did not
The commit landed at 03:43 UTC on a Thursday, late morning for maintainers in UTC+8. Timing gave nothing away (fig. 5). The author did: in more than seven thousand commits since 2017, github-actions[bot] had never authored a commit on master before (fig. 4). A bot account that never commits, adding executable editor configuration with a recycled message, is a strong signal on its own terms.
github-actions[bot] has authored exactly one: the worm's.Data table
| Bot account | Commits on master |
|---|---|
depfu[bot] | 18 |
dependabot-preview[bot] | 16 |
dependabot[bot] | 12 |
copilot-swe-agent[bot] | 3 |
imgbot[bot] | 2 |
github-actions[bot] | 1 |
lgtm-com[bot] | 1 |
Data table
| Weekday | Commits | Busiest hour (UTC) |
|---|---|---|
| Mon | 33 | 13 |
| Tue | 30 | 10 |
| Wed | 48 | 5 |
| Thu | 29 | 1 |
| Fri | 115 | 10 |
| Sat | 46 | 3 |
| Sun | 51 | 9 |
What the payload is
We decoded .claude/index.js statically, without running it. It is the build used in the May 2026 AntV and SAP wave attributed to TeamPCP (MITRE S9043). It shares the exfiltration endpoint and repository markers Corgea published in May, and the gh-token-monitor component SafeDep described.
- It carries the commit. The script holds encrypted copies of
setup.mjs,settings.jsonandtasks.jsonthat are byte-identical to the committed files, and commits them, plus itself, to up to 50 branches with the job token. That is the burst in fig. 1. - It steals credentials from files, environment variables, the CI runner's memory, cloud secret stores, Kubernetes, Vault and local password managers, and sends them to a C2 disguised as OpenTelemetry traffic, with public GitHub repositories as a fallback.
- It defends itself.
gh-token-monitorpolls GitHub every minute and runsrm -rf ~/if the stolen token stops working. In this build it arms when the token's owner belongs to no GitHub organisation. - It is not perfect. In our reading,
setup.mjsonly runs the payload on machines that do not already have Bun installed.
Why our commit monitoring missed it
We watch this repository's commits. Our rules looked for install scripts, build configuration and workflow changes. They had no rule for .claude/ or .vscode/ files, and they treated Markdown as documentation. So the worm's commit, which touched nothing else, produced no alert. The next day we did alert, on the maintainers' own clean-up commit, because it added a pre-publish check. Rules for agent autorun hooks, VS Code folder-open tasks and agent instruction files are now in review, tested against this commit and the earlier keyv one. We also did not watch GitHub Action tags, which is how this one got in.
Indicators of compromise
| Type | Indicator | Context |
|---|---|---|
| GitHub Action | ||
| Action ref | actions-cool/issues-helper@v2.2.0 = 896379edfaac994ff214a3097c6c82e21be16066 | Resolved in run #8451 (CyberXYZ, from the run log) |
| Action ref | actions-cool/issues-helper@v2.2.1 = a0c53dd42fc842d2f9276c5a1d4f9a26abe8713d | Socket |
| Commits in ant-design/pro-components (24 Sep 2026, github-actions[bot]) | ||
| v1 | 8bc3db8926d195982ba68750a3e801ad197dd5f9 | Branch tip until 15 Oct (removed) |
| master | 1bc4049d295a4af4da3cef8074e4b5d7312a6858 | Reverted in 58c10838 |
| main | dc544e247e895f75c9e49bf3c52dffb38cc8a88e | Force-pushed clean 26 Sep |
| gh-pages | 2b73780cd1a90fa24ee61b361fca2a96f068952b | Branch tip until 15 Oct (removed) |
| feat/layout-refactor | d749366171d83e7b3ced108330bde32068eff837 | Branch tip until 15 Oct (removed) |
| experimental | e990fbb926446684ebf06f9f85a62cea8ee4d2cd | Branch tip until 15 Oct (removed) |
| Files (SHA-256) | ||
.claude/index.js | 26bc468790d3fa35c0e84423165fcf555bd4da6b5fd5f6d5b349262bd9f2b8aa | 500,143 bytes; git blob 2931c1be43b0d04174636ddbff54963aec92bdeb |
.claude/setup.mjs, .vscode/setup.mjs | 2be36c46d3f421bbd89bcb94997afb85020a672d82b64114637741411d7fc919 | Bun loader, same file twice |
.claude/settings.json | 9566275be80af10b8c6be1d47bfcfdbe5590f472b1a9abd85bad24e0a9a7a891 | SessionStart hook: node .claude/setup.mjs |
.vscode/tasks.json | 927387d0cfac1118df4b383decc2ea6ba49c9d2f98b47098bcbcba1efc026e1f | runOn: folderOpen; identical to the file in the August keyv commit |
| Network and markers | ||
| Domain | t[.]m-kosche[.]com | Exfiltration, path /api/public/otel/v1/traces (Corgea, May 2026) |
| Repo description | niagA oG eW ereH :duluH-iahS | Fallback exfiltration repositories (Corgea) |
| On developer machines | ||
| Persistence | ~/.local/share/kitty/cat.py, kitty-monitor service | Signed remote-command backdoor (Corgea; our decode) |
| Dead man's switch | ~/.local/bin/gh-token-monitor.sh, ~/.config/gh-token-monitor/ | Wipes the home directory on token revocation (SafeDep; our decode) |
| State file | /var/tmp/.gh_update_state | Our decode |
Check a clone and a machine
Disconnect the machine first. Remove gh-token-monitor before you revoke any GitHub token, then remove kitty-monitor, then rotate GitHub, npm, cloud and SSH credentials from a different, clean machine.
Find the same entry point in your own workflows
The worm did not need a stolen password. It needed three things: a workflow anyone could start by opening an issue, a third-party action pinned to a tag, and a token with write access. Our CLI checks for that shape locally. The CI checks are built in, so there is no engine to install and no account needed.
Run against the two issue workflows on the pro-components master branch on 14 October, it reports seven findings, two of them high:
To turn it into a gate, add xyz code-scan ci . --fail-on high to your pipeline. It exits non-zero on any high finding. Add --format sarif -o xyz-ci.sarif to send the results to GitHub code scanning. Today the CLI flags the tag pin and the missing permissions, but it does not yet know that this particular action was hijacked. Two new rules are in review: one names known-compromised actions, and one flags agent and editor autorun files like the five above.
What to change
- Pin every third-party action to a full commit SHA, and replace actions whose repositories were compromised. A tag is a pointer anyone with write access can move.
- Declare
permissions:on every workflow. An issue-labelling job needsissues: write, not write access to contents, actions and pages. - Protect or delete long-lived branches. The four branches the clean-up missed had not been touched in months or years. Nobody was looking at them.
- Treat editor and agent configuration in a repository as code. Review
.claude/,.vscode/tasks.jsonand similar files in pull requests, settask.allowAutomaticTaskstooffin VS Code, and read a repository's Claude Code hooks before you trust it. - Act on the flag. An AI reviewer marked these files for removal and a security researcher filed an issue within hours. Both signals were there; what was missing was someone assigned to them.
MITRE ATT&CK
References
- CyberXYZRun-log reconstruction, branch survey (re-checked 14 Oct 2026) and static decode of
.claude/index.js - Aikidoant-design/pro-components issue #9704: This repository is compromised (24 Sep 2026)
- SafeDepMini Shai-Hulud reinfection of GitHub repositories (24 Sep 2026)
- SocketCompromised GitHub Actions re-enabled with the Mini Shai-Hulud payload (24 Sep 2026)
- CorgeaMini Shai-Hulud npm worm: the AntV wave (19 May 2026)
- The Hacker NewsCompromised GitHub Actions came back
- BleepingComputerGitHub Actions re-enabled with Mini Shai-Hulud payload still active
- GitHubpro-components Actions run #8451 and pull request #9702
- OSVMAL-2026-11524 (keyv, the same five-file shape, August 2026)
- MITRES9043, T1195.001 Compromise Software Dependencies and Development Tools
Let's Talk
Want to learn how CyberXYZ protects your supply chain? We'd love to hear from you.