Back to all posts
Critical Malware

Mini Shai-Hulud in ant-design/pro-components:
a bug report woke a hijacked Action, six branches fell in seven seconds, and four kept the worm for three weeks

On 24 September 2026 a poisoned GitHub Action in the pro-components issue workflow committed the Mini Shai-Hulud worm to six branches. Aikido and SafeDep reported the default-branch commit the same day. From the repository's own run logs we reconstruct the full run, and we found four branches the clean-up had missed for three weeks. The maintainers removed them within hours of our report.

CyberXYZ Security Team Threat Intelligence
11 min read
If you work on ant-design/pro-components

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

Credit

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 is new

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.

new

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.

new

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.

new

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.

new

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.

new

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.

new

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.

03:43:5603:43:5803:44:0003:44:0203:44:0403:44:06v1cleaned 15 Oct, after reportmastercleaned 25 Sep 08:35maincleaned 26 Sep 12:07gh-pagescleaned 15 Oct, after reportfeat/layout-refactorcleaned 15 Oct, after reportexperimentalcleaned 15 Oct, after report
fig. 1 · the six commits 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
BranchCommitPushed (UTC)Cleaned
v18bc3db8926d103:43:5815 Oct 02:06 UTC, after our report
master1bc4049d295a03:43:59cleaned, revert push 2026-09-25T08:35:48Z
maindc544e247e8903:44:00cleaned, force-push 2026-09-26T12:07:09Z
gh-pages2b73780cd1a903:44:0215 Oct 02:06 UTC, after our report
feat/layout-refactord749366171d803:44:0415 Oct 02:06 UTC, after our report
experimentale990fbb9264403:44:0515 Oct 02:06 UTC, after our report
The run

How a bug report pushed malware to six branches

1
May: the action's tags are moved

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).

2
16 September: the action comes back

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.

3
24 September 03:41: a user opens a bug report

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.

4
The composite action installs Bun and runs the worm

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.

5
03:43:58 to 03:44:05: six commits

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.

6
The next developer to open the folder runs it

.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.

0 s60 s120 s180 sAprMayJunJulAugSepOct3 runs of the hijacked action: 151 to 170 saction blocked: runs fail in 5 to 9 s
fig. 2 · duration of every “Issue Open Check” run from April to October 2026 (100 runs). Blue: normal runs, 5 to 19 s. Grey: failed runs while GitHub had blocked the action. Orange: the three runs that executed the hijacked actions-cool/issues-helper@v2.2.0 (commit 896379ed). Source: GitHub Actions run metadata and logs.
Data table
RunStarted (UTC)DurationTriggered by
#84502026-09-18 07:25153 s🐛[BUG] ProTable 默认密度按钮触发 Function components cannot be given
#84512026-09-24 03:41170 s🐛[BUG] ProForm 容器销毁重建后 formRef.current.getFieldsFormatValue(
#84522026-09-24 06:53151 sThis repository is compromised
Exposure

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.

24 Sep1 Oct8 Oct15 Octmaster28 h 52 minmain56 h 23 minv120 d 22 hgh-pages20 d 22 hfeat/layout-refactor20 d 22 hexperimental20 d 22 h
fig. 3 · how long each branch carried the files. master was reverted after 28 h 52 min and main force-pushed clean after 56 h 23 min. The other four kept them for 20 days 22 hours, until the maintainers removed them on 15 October after our report.
Data table
BranchInfected from (UTC)Cleaned (UTC)Exposure
master2026-09-24 03:43:592026-09-25 08:35:4828 h 52 min
main2026-09-24 03:44:002026-09-26 12:07:0956 h 23 min
v12026-09-24 03:43:582026-10-15 02:06:0920 d 22 h
gh-pages2026-09-24 03:44:022026-10-15 02:06:0020 d 22 h
feat/layout-refactor2026-09-24 03:44:042026-10-15 02:05:5820 d 22 h
experimental2026-09-24 03:44:052026-10-15 02:05:5220 d 22 h
Signals

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.

depfu[bot]18dependabot-preview[bot]16dependabot[bot]12copilot-swe-agent[bot]3imgbot[bot]2github-actions[bot]1 (the worm)lgtm-com[bot]1
fig. 4 · commits on master authored by bot accounts since the repository began in August 2017 (7,281 commits in total). github-actions[bot] has authored exactly one: the worm's.
Data table
Bot accountCommits 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
0003060912151821MonMon 00:00 UTC, 0 commitsMon 01:00 UTC, 2 commitsMon 02:00 UTC, 1 commitsMon 03:00 UTC, 0 commitsMon 04:00 UTC, 1 commitsMon 05:00 UTC, 0 commitsMon 06:00 UTC, 0 commitsMon 07:00 UTC, 0 commitsMon 08:00 UTC, 2 commitsMon 09:00 UTC, 2 commitsMon 10:00 UTC, 0 commitsMon 11:00 UTC, 0 commitsMon 12:00 UTC, 2 commitsMon 13:00 UTC, 7 commitsMon 14:00 UTC, 3 commitsMon 15:00 UTC, 0 commitsMon 16:00 UTC, 4 commitsMon 17:00 UTC, 5 commitsMon 18:00 UTC, 1 commitsMon 19:00 UTC, 1 commitsMon 20:00 UTC, 2 commitsMon 21:00 UTC, 0 commitsMon 22:00 UTC, 0 commitsMon 23:00 UTC, 0 commitsTueTue 00:00 UTC, 0 commitsTue 01:00 UTC, 1 commitsTue 02:00 UTC, 1 commitsTue 03:00 UTC, 0 commitsTue 04:00 UTC, 0 commitsTue 05:00 UTC, 3 commitsTue 06:00 UTC, 2 commitsTue 07:00 UTC, 1 commitsTue 08:00 UTC, 1 commitsTue 09:00 UTC, 3 commitsTue 10:00 UTC, 8 commitsTue 11:00 UTC, 2 commitsTue 12:00 UTC, 2 commitsTue 13:00 UTC, 4 commitsTue 14:00 UTC, 0 commitsTue 15:00 UTC, 0 commitsTue 16:00 UTC, 0 commitsTue 17:00 UTC, 0 commitsTue 18:00 UTC, 0 commitsTue 19:00 UTC, 2 commitsTue 20:00 UTC, 0 commitsTue 21:00 UTC, 0 commitsTue 22:00 UTC, 0 commitsTue 23:00 UTC, 0 commitsWedWed 00:00 UTC, 1 commitsWed 01:00 UTC, 4 commitsWed 02:00 UTC, 1 commitsWed 03:00 UTC, 1 commitsWed 04:00 UTC, 5 commitsWed 05:00 UTC, 7 commitsWed 06:00 UTC, 4 commitsWed 07:00 UTC, 5 commitsWed 08:00 UTC, 6 commitsWed 09:00 UTC, 5 commitsWed 10:00 UTC, 3 commitsWed 11:00 UTC, 2 commitsWed 12:00 UTC, 3 commitsWed 13:00 UTC, 0 commitsWed 14:00 UTC, 1 commitsWed 15:00 UTC, 0 commitsWed 16:00 UTC, 0 commitsWed 17:00 UTC, 0 commitsWed 18:00 UTC, 0 commitsWed 19:00 UTC, 0 commitsWed 20:00 UTC, 0 commitsWed 21:00 UTC, 0 commitsWed 22:00 UTC, 0 commitsWed 23:00 UTC, 0 commitsThuThu 00:00 UTC, 1 commitsThu 01:00 UTC, 4 commitsThu 02:00 UTC, 2 commitsThu 03:00 UTC, 2 commitsThu 04:00 UTC, 0 commitsThu 05:00 UTC, 2 commitsThu 06:00 UTC, 4 commitsThu 07:00 UTC, 0 commitsThu 08:00 UTC, 3 commitsThu 09:00 UTC, 0 commitsThu 10:00 UTC, 2 commitsThu 11:00 UTC, 0 commitsThu 12:00 UTC, 1 commitsThu 13:00 UTC, 1 commitsThu 14:00 UTC, 0 commitsThu 15:00 UTC, 0 commitsThu 16:00 UTC, 0 commitsThu 17:00 UTC, 0 commitsThu 18:00 UTC, 0 commitsThu 19:00 UTC, 0 commitsThu 20:00 UTC, 4 commitsThu 21:00 UTC, 3 commitsThu 22:00 UTC, 0 commitsThu 23:00 UTC, 0 commitsFriFri 00:00 UTC, 0 commitsFri 01:00 UTC, 0 commitsFri 02:00 UTC, 4 commitsFri 03:00 UTC, 5 commitsFri 04:00 UTC, 1 commitsFri 05:00 UTC, 1 commitsFri 06:00 UTC, 1 commitsFri 07:00 UTC, 1 commitsFri 08:00 UTC, 2 commitsFri 09:00 UTC, 13 commitsFri 10:00 UTC, 21 commitsFri 11:00 UTC, 9 commitsFri 12:00 UTC, 2 commitsFri 13:00 UTC, 13 commitsFri 14:00 UTC, 17 commitsFri 15:00 UTC, 11 commitsFri 16:00 UTC, 9 commitsFri 17:00 UTC, 3 commitsFri 18:00 UTC, 1 commitsFri 19:00 UTC, 1 commitsFri 20:00 UTC, 0 commitsFri 21:00 UTC, 0 commitsFri 22:00 UTC, 0 commitsFri 23:00 UTC, 0 commitsSatSat 00:00 UTC, 0 commitsSat 01:00 UTC, 1 commitsSat 02:00 UTC, 0 commitsSat 03:00 UTC, 6 commitsSat 04:00 UTC, 6 commitsSat 05:00 UTC, 1 commitsSat 06:00 UTC, 2 commitsSat 07:00 UTC, 6 commitsSat 08:00 UTC, 1 commitsSat 09:00 UTC, 2 commitsSat 10:00 UTC, 5 commitsSat 11:00 UTC, 0 commitsSat 12:00 UTC, 4 commitsSat 13:00 UTC, 4 commitsSat 14:00 UTC, 4 commitsSat 15:00 UTC, 1 commitsSat 16:00 UTC, 1 commitsSat 17:00 UTC, 2 commitsSat 18:00 UTC, 0 commitsSat 19:00 UTC, 0 commitsSat 20:00 UTC, 0 commitsSat 21:00 UTC, 0 commitsSat 22:00 UTC, 0 commitsSat 23:00 UTC, 0 commitsSunSun 00:00 UTC, 0 commitsSun 01:00 UTC, 2 commitsSun 02:00 UTC, 2 commitsSun 03:00 UTC, 5 commitsSun 04:00 UTC, 1 commitsSun 05:00 UTC, 1 commitsSun 06:00 UTC, 2 commitsSun 07:00 UTC, 0 commitsSun 08:00 UTC, 4 commitsSun 09:00 UTC, 9 commitsSun 10:00 UTC, 4 commitsSun 11:00 UTC, 4 commitsSun 12:00 UTC, 3 commitsSun 13:00 UTC, 2 commitsSun 14:00 UTC, 2 commitsSun 15:00 UTC, 5 commitsSun 16:00 UTC, 2 commitsSun 17:00 UTC, 3 commitsSun 18:00 UTC, 0 commitsSun 19:00 UTC, 0 commitsSun 20:00 UTC, 0 commitsSun 21:00 UTC, 0 commitsSun 22:00 UTC, 0 commitsSun 23:00 UTC, 0 commitsthe worm, Thu 03:43 UTChour of day (UTC) · maintainers work in UTC+8, so 03 UTC is late morning for them
fig. 5 · human commits on master by weekday and hour (UTC) over the 180 days to 14 October 2026 (352 commits). The worm's commit landed in an ordinary working hour. Timing gave nothing away; the author did (fig. 4).
Data table
WeekdayCommitsBusiest hour (UTC)
Mon3313
Tue3010
Wed485
Thu291
Fri11510
Sat463
Sun519
Payload

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.

Accountability

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

Indicators of compromise

TypeIndicatorContext
GitHub Action
Action refactions-cool/issues-helper@v2.2.0 = 896379edfaac994ff214a3097c6c82e21be16066Resolved in run #8451 (CyberXYZ, from the run log)
Action refactions-cool/issues-helper@v2.2.1 = a0c53dd42fc842d2f9276c5a1d4f9a26abe8713dSocket
Commits in ant-design/pro-components (24 Sep 2026, github-actions[bot])
v18bc3db8926d195982ba68750a3e801ad197dd5f9Branch tip until 15 Oct (removed)
master1bc4049d295a4af4da3cef8074e4b5d7312a6858Reverted in 58c10838
maindc544e247e895f75c9e49bf3c52dffb38cc8a88eForce-pushed clean 26 Sep
gh-pages2b73780cd1a90fa24ee61b361fca2a96f068952bBranch tip until 15 Oct (removed)
feat/layout-refactord749366171d83e7b3ced108330bde32068eff837Branch tip until 15 Oct (removed)
experimentale990fbb926446684ebf06f9f85a62cea8ee4d2cdBranch tip until 15 Oct (removed)
Files (SHA-256)
.claude/index.js26bc468790d3fa35c0e84423165fcf555bd4da6b5fd5f6d5b349262bd9f2b8aa500,143 bytes; git blob 2931c1be43b0d04174636ddbff54963aec92bdeb
.claude/setup.mjs, .vscode/setup.mjs2be36c46d3f421bbd89bcb94997afb85020a672d82b64114637741411d7fc919Bun loader, same file twice
.claude/settings.json9566275be80af10b8c6be1d47bfcfdbe5590f472b1a9abd85bad24e0a9a7a891SessionStart hook: node .claude/setup.mjs
.vscode/tasks.json927387d0cfac1118df4b383decc2ea6ba49c9d2f98b47098bcbcba1efc026e1frunOn: folderOpen; identical to the file in the August keyv commit
Network and markers
Domaint[.]m-kosche[.]comExfiltration, path /api/public/otel/v1/traces (Corgea, May 2026)
Repo descriptionniagA oG eW ereH :duluH-iahSFallback exfiltration repositories (Corgea)
On developer machines
Persistence~/.local/share/kitty/cat.py, kitty-monitor serviceSigned 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_stateOur decode
How to check

Check a clone and a machine

Order matters

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.

in a clone of the repositorygit fetch --all git log --all --format='%h %an %ad %s' --date=iso -- .claude/index.js .vscode/tasks.json
on a macOS or Linux machinels -la ~/.local/bin/gh-token-monitor.sh ~/.config/gh-token-monitor ~/.local/share/kitty/cat.py /var/tmp/.gh_update_state 2>/dev/null ls ~/Library/LaunchAgents 2>/dev/null | grep -E 'gh-token-monitor|kitty-monitor' systemctl --user list-unit-files 2>/dev/null | grep -E 'gh-token-monitor|kitty-monitor' grep -n 'index.js' ~/.claude/settings.json 2>/dev/null
Check your workflows

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.

install, then scan a repositorypip install cyberxyz-scanner xyz code-scan ci . --no-upload

Run against the two issue workflows on the pro-components master branch on 14 October, it reports seven findings, two of them high:

xyz code-scan ci · ant-design/pro-components master · 14 Oct 2026 · action owner: actions-coolHIGH ci-secrets-to-unpinned-action issue-labeled.yml:13,27 issues-helper@v1.11 MEDIUM ci-unpinned-action issue-open-check.yml:11,19 issues-helper@v2.2.0 MEDIUM ci-unpinned-action issue-open-check.yml:29 issues-similarity-analysis@v1.1.0 MEDIUM ci-permissions-missing issue-open-check.yml:3 no permissions: block MEDIUM ci-permissions-missing issue-labeled.yml:3 no permissions: block

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.

Guidance

What to change

Mapping

MITRE ATT&CK

Initial AccessT1195.001Compromise Software Dependencies and Development Tools
Credential AccessT1528Steal Application Access Token
Credential AccessT1552.001Credentials In Files
PersistenceT1546Event Triggered Execution
ExfiltrationT1041Exfiltration Over C2 Channel
ImpactT1485Data Destruction

References

  1. CyberXYZRun-log reconstruction, branch survey (re-checked 14 Oct 2026) and static decode of .claude/index.js
  2. Aikidoant-design/pro-components issue #9704: This repository is compromised (24 Sep 2026)
  3. SafeDepMini Shai-Hulud reinfection of GitHub repositories (24 Sep 2026)
  4. SocketCompromised GitHub Actions re-enabled with the Mini Shai-Hulud payload (24 Sep 2026)
  5. CorgeaMini Shai-Hulud npm worm: the AntV wave (19 May 2026)
  6. The Hacker NewsCompromised GitHub Actions came back
  7. BleepingComputerGitHub Actions re-enabled with Mini Shai-Hulud payload still active
  8. GitHubpro-components Actions run #8451 and pull request #9702
  9. OSVMAL-2026-11524 (keyv, the same five-file shape, August 2026)
  10. 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.