Back to all posts
Critical Research

The long tail of Mini Shai-Hulud:
24 more repositories the first reports missed

Socket, SafeDep and Aikido exposed the hijacked actions-cool GitHub Actions in September, and SafeDep listed six infected repositories. We read 74 days of GitHub Archive and the workflow files behind packages we track, and found the worm on 24 more, element-plus, metersphere, nutui-react and alibaba/hooks among them, plus four projects still wired to the action. On 20 October, 23 of the 24 still carried it.

CyberXYZ Security Team Threat Intelligence
15 min read
If you work on alibaba/hooks, metersphere or nutui-react

On 20 October 2026, 18:30 to 18:37 UTC the worm's files were still on 16 branches of alibaba/hooks, 24 branches of metersphere/metersphere (39 of its 44 counting an older June build of the same worm) and 41 branches of jdf2e/nutui-react. Their default branches are clean. Do not open a checkout of any other branch in Claude Code, VS Code or Cursor until you have checked it. If you already have, 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. We did not check whether any npm release was built from an infected branch.

Summary

Credit

Who reported this first

Socket showed that GitHub re-enabled both actions on 16 September with their May tags still pointing at malicious commits, and recorded that GitHub disabled them again on 25 September. SafeDep listed six repositories that received the worm's hook files between 20 and 24 September: jd-opensource/micro-app, MaaXYZ/MaaFramework, ant-design/pro-components, Moonofweisheng/wot-design-uni, vbenjs/vben-admin-thin-next and nineaiyu/xadmin-client. Aikido opened issue #9704 on pro-components the morning it was hit, and our earlier post reconstructed that run from the repository's own logs. The older June build of the worm that we also found on metersphere was reported first by StepSecurity and then by SafeDep (see below).

SafeDep built its list with GitHub code search and said it could miss repositories. The gap turned out to be structural. In each of the four infected organisation projects we name below, the default branch is clean today and the worm sits only on other branches, which GitHub code search does not index.

We reported every project we name here before publishing. Personal forks and repositories we are still reporting appear only as counts.

What is new

What the first reports did not name

new

22 repositories in GitHub Archive

70 worm push bursts landed in 25 repositories between 20 and 24 September. Only 3 of the 25 appear in the public reports.

new

element-plus, since 19 May

27 branches written in the first wave in May. We found them by checking the upstream of an infected fork. Fixed on 20 October.

new

alibaba/hooks, since 19 May

16 branches carry the worm, and two of its issue workflows still call actions-cool/issues-helper@v3 by tag.

new

Four more still wired to it

umi, dumi, G6 and ant-design-charts have no worm files, but their workflows still call the hijacked action by tag. Three had a successful run while it was live.

new

Two generations on metersphere

20 branches also carry the worm's June build, on commits forged in real developers' names. On 5 of them the September worm landed on top.

new

Most of it is still there

On 20 October 23 of the 24 still carried the worm. 17 of the 19 personal forks have it on their default branch.

Method

How we looked

GH Archive publishes every public GitHub event, hour by hour. We read all 1,776 hourly files from 1 August to 13 October 2026, 161,819,014 events and 36.4 GB compressed, and looked for the shape the worm leaves: github-actions[bot] pushing three or more branches of one repository within 15 seconds. Plenty of ordinary automation makes that shape too: on its own it was the worm only 0.66% of the time.

We checked each burst without downloading anything that could run. We asked raw.githubusercontent.com only for the HTTP status of .claude/settings.json and .vscode/tasks.json at the pushed commit. Then we fetched the commit objects alone, with no trees or files, to read the author and message. A burst counted when both files were present and the commit was a github-actions[bot] commit reusing its parent's message, which is how the payload writes.

161,819,014
GH Archive events, 1 Aug to 13 Oct
11,584,131
pushes by github-actions[bot]
10,546
bursts: 3+ branches of one repo in 15 s
84
bursts with both worm configs at the pushed commit
70
... and a bot commit reusing its parent's message
bar length on a log scale
fig. 1 · from every public GitHub event between 1 August and 13 October 2026 to the 70 worm runs. Orange: bursts confirmed as the worm. Source: GH Archive hourly files, tokenless HTTP status checks and commit metadata (CyberXYZ).
Data table
StageCount
GH Archive events, 1 Aug to 13 Oct161,819,014
pushes by github-actions[bot]11,584,131
bursts: 3+ branches of one repo in 15 s10,546
bursts with both worm configs at the pushed commit84
... and a bot commit reusing its parent's message70

70 bursts in 25 repositories passed, 329 branch pushes in all. Every one falls between 20 Sep 02:52 and 24 Sep 21:12 UTC, inside the window when GitHub had re-enabled the actions. The hunt found none outside it.

0481216 Sep18 Sep20 Sep22 Sep24 Sep26 Sepactions re-enabled 16 Sepdisabled again 25 Sep
fig. 2 · the 70 worm push bursts by start time (UTC) and number of branches pushed. Orange: 65 bursts in the 22 repositories no public report named. Blue: 5 bursts in the 3 that SafeDep or Socket named. GitHub re-enabled the actions on 16 September and disabled them again on 25 September; the first burst came on 20 September. Source: GH Archive, grouped with the rule our detector uses.
Data table
GroupBurstsBranches pushedFirst (UTC)Last (UTC)
metersphere/metersphere1421 Sep 00:1421 Sep 00:14
jdf2e/nutui-react11021 Sep 07:3121 Sep 07:31
19 personal forks and 1 repository we are still reporting6329720 Sep 02:5224 Sep 21:12
3 repositories SafeDep or Socket named51820 Sep 16:1624 Sep 07:03

GH Archive is a partial feed. Two of SafeDep's six repositories, pro-components among them, have no events in it for their worm runs, and a third shows a single push, so the hunt found only three of SafeDep's six. Every count in this post is a floor.

Two more repositories came from outside GH Archive. One of the 22 is a personal fork of element-plus, and checking its upstream found element-plus/element-plus itself, infected in May. A separate sweep of the workflow files of repositories behind packages we track found alibaba/hooks, also infected in May, and the four projects still wired to the action.

Findings

Where the worm is, and who has cleaned up

Branch counts below compare .claude/settings.json and .vscode/tasks.json at every branch head with the worm's files by SHA-256. Workflow counts are lines on the default branch that call actions-cool/issues-helper@ or actions-cool/maintain-one-comment@ by tag; the pinned -backup forks are not counted. We re-checked everything on 20 October 2026, 18:30 to 18:37 UTC, logged out, with git ls-remote and raw.githubusercontent.com.

RepositoryFound byWorm branches, 17 OctWorm branches, 20 OctCalls the action by tag, 20 OctReport
element-plus/element-plusUpstream of an infected fork27 of 1000 of 740 (uses the pinned -backup fork)Emailed 17 Oct. Fixed 20 Oct
alibaba/hooksWorkflow sweep16 of 2816 of 283Reported privately 17 Oct
metersphere/metersphereGH Archive24 of 4424 of 44 (39 with the June build)0Reported privately 17 Oct
jdf2e/nutui-reactGH Archive41 of 4941 of 492Team address bounced; maintainers emailed 17 Oct; no reply
umijs/umiWorkflow sweep0 of 2760 of 2753Reported privately 17 Oct
umijs/dumiWorkflow sweep0 of 230 of 232Reported privately 17 Oct
antvis/G6Workflow sweep0 of 170 of 189Reported privately 17 Oct
ant-design/ant-design-chartsWorkflow sweep0 of 190 of 199Added to Ant Design's existing advisory
19 personal or abandoned forksGH Archive416 branches (14 Oct)416 branches; 17 default branchesnot countedReported to GitHub 17 Oct
1 more organisation repositoryGH Archivenot yet reported47 of 78not countedBeing reported
element-plus/element-plus27 on 17 Oct0 on 20 Oct, branches deletedjdf2e/nutui-react41 on 17 Oct41 on 20 Octmetersphere/metersphere24 on 17 Oct24 on 20 Octalibaba/hooks16 on 17 Oct16 on 20 Oct
fig. 3 · branches whose worm config files match by SHA-256, on 17 October, when we reported, and on 20 October. Grey: day of the report. Orange: still there. Blue: cleaned. The 15 metersphere branches that carry only the worm's June build are not counted. Source: tokenless re-checks (git ls-remote and raw.githubusercontent.com).
Data table
Repository17 Oct20 Oct
element-plus/element-plus270
jdf2e/nutui-react4141
metersphere/metersphere2424
alibaba/hooks1616

element-plus/element-plus: fixed

The worm wrote 27 branches of element-plus, among them next, generic and perf/table, in two runs about 30 seconds apart at 00:07 UTC on 19 May 2026, the day GitHub blocked the actions. The maintainers pinned every action to a commit the same day, which closed the entry point, but the branches stayed. The project has no security policy and private reporting was off, so on 17 October we emailed the maintainers listed on npm. They deleted all 27 branches, and our re-check at 16:10 UTC on 20 October found none of the remaining 74 infected. Thank you to the element-plus maintainers for the quick fix, and for letting us name the project.

alibaba/hooks: 16 branches since May, and still wired to the action

16 of 28 branches carry the worm, written between 00:28 and 00:31 UTC on 19 May 2026. The default branch, master, is clean. On 20 May the maintainers removed the action from one issue workflow, but two others still call actions-cool/issues-helper@v3 by tag, in three places. Today only GitHub's block on the action stops those runs. We reported it privately on 17 October. Nothing had changed when we re-checked on 20 October.

MayJunJulAugSepOctelement-plus154 d 16 halibaba/hooksstill theremeterspherestill therenutui-reactstill there19 personal forksstill therepro-components20 d 22 h
fig. 4 · from the first worm commit we know of to the clean-up, or to our re-check on 20 October. Orange: still carrying the files. Blue: cleaned. element-plus, alibaba/hooks and metersphere's dev branch were first written on 19 May, in the first wave; the rest in September. pro-components, from our earlier post, for scale. Source: commit metadata, GH Archive and tokenless re-checks.
Data table
ItemFrom (UTC)To (UTC)Exposure
element-plus2026-05-19 00:07:092026-10-20 16:10:00154 d 16 h
alibaba/hooks2026-05-19 00:28:59not yetstill there on 20 Oct, 154 d 18 h
metersphere2026-05-19 00:29:30not yetstill there on 20 Oct, 154 d 18 h
nutui-react2026-09-21 07:31:16not yetstill there on 20 Oct, 29 d 11 h
19 personal forks2026-09-20 02:52:22not yetstill there on 20 Oct, 30 d 15 h
pro-components2026-09-24 03:43:582026-10-15 02:06:0920 d 22 h

metersphere/metersphere: two generations of the worm on the same branches

The actions-cool worm first wrote metersphere's dev branch at 00:29 UTC on 19 May 2026. It came back on 21 September, when GH Archive shows github-actions[bot] pushing to four branches within 17 seconds at 00:14:30 UTC. On 20 October, 24 of the 44 branches carried its files, including dev and every v1 release branch. The default branch, v3.x, is clean, and no workflow on it uses actions-cool today.

20 branches also carry an older generation: the June 2026 build of the same worm kit, known as Miasma, which StepSecurity reported first on 3 June. Its hooks run .github/setup.js, and it also plants .gemini/settings.json and .cursor/rules/setup.mdc. SafeDep documented how it forges commits under a maintainer's own name, and its list of infected repositories includes metersphere/helm-chart, but not metersphere/metersphere.

What we add is small. The June build sits on 20 branches of metersphere/metersphere itself. It arrived in June 2026, not through the CI action. Each commit copies its parent's author name, email and date, appends [skip ci] to the parent's message and is unsigned, so the history shows the name and email of a real maintainer, on commits dated as far back as December 2021. On 5 branches, v1.15 and v1.17 to v1.20, the September worm then landed on top: two generations, four months apart, arriving by different routes. Counting both, 39 of the 44 branches carry one worm or the other, and all 39 are in our private report to the maintainers, filed on 17 October.

jdf2e/nutui-react: 41 branches, no reply yet

GH Archive saw one burst, 10 branches in 37 seconds at 07:31 UTC on 21 September; on 20 October, 41 of 49 branches carried the worm. The default branch, feat_v4.x, is clean, but two of its issue workflows still call actions-cool/issues-helper@v3 by tag. The public run list shows a successful run of one of them in each window when the action was live. The project has no security policy and private reporting is off. Its team address bounced on 17 October, so we emailed the maintainers who publish its npm package. We have had no reply.

Four projects still wired to the hijacked action

The workflow sweep found four projects with no worm files on any branch whose default-branch workflows still call the hijacked action by tag:

Three of them had a successful run of the affected workflow while the action was live. umi's Issue Checker (run 3268) and G6's Mark Duplicate Issue (run 1545) ran between 16 and 25 September, and dumi's Issue Reply (run 1381) on 18 or 19 May. A successful run does not prove the payload ran, and no worm files reached their branches. Only the run logs can settle it, so we recommended rotating any secret those workflows could reach. ant-design-charts shows no run in either window. If GitHub re-enables the actions again, the next issue event runs the payload in all four. We reported umi, dumi and G6 privately on 17 October, and added ant-design-charts to Ant Design's existing private advisory. G6's npm package, @antv/g6, was among those compromised in the separate May npm wave; this finding concerns its GitHub workflows.

19 personal and abandoned forks

19 of the 22 repositories we found in GH Archive are personal forks: 12 of ant-design and seven of other projects. They kept the upstream's issue workflows, and most look abandoned. The ant-design forks were written in the same minutes, at 02:52 and 05:56 UTC on 20 September and 07:53 and 11:06 UTC on 22 September, which fits a scheduled workflow they share. On 20 October all 19 still carried the worm, on 416 branches in all, and in 17 of them the default branch is infected. None has a security contact or open issues, so we reported them to GitHub on 17 October. We do not name personal accounts.

Two more we are still reporting

We are still reporting two more organisation repositories. One carries the worm on its branches and is counted in the 24. The other is not exposed. We will name neither until the owners have been told.

Exfiltration repositories

The payload falls back to exfiltrating through public repositories it creates with a stolen token, under fixed descriptions such as the reversed niagA oG eW ereH :duluH-iahS that Corgea documented in May. The same hunt matched 866 new repositories under 13 accounts whose whole description is one of the worm's markers, created between 4 August and 13 October across several waves. One account alone holds 767, created between 20 and 30 September. We did not open them, and we do not name the accounts: they belong to victims.

Lessons

Why the worm outlived the clean-up

Detection

What we run now

The two checks behind this hunt went into our hourly GH Archive pipeline on 19 October. One looks for the burst shape and verifies each burst the same way, with HTTP status checks and commit metadata only. The other flags any new repository whose whole description is one of the worm's exfiltration markers. On the hunt window, a burst with both files at the pushed commit was the worm 83% of the time (70 of 84). Adding the copied-message check kept 70 of 70, but that figure is partly circular, since the same evidence confirmed them. The independent check is timing: all 70 fall inside the window when the actions were live. Findings go to our analysts for review. They do not alert customers on their own.

A second job reads the workflow files of repositories behind the packages we track and flags any that call a known-compromised action. On 17 October it flagged alibaba/hooks, ant-design-charts and G6 on its own, with eight HIGH findings.

Indicators

Indicators of compromise

TypeIndicatorContext
GitHub Actions
Actionactions-cool/issues-helper, every tagAll tags moved to malicious commits on 18 May 2026 (SafeDep: 53 tags); repository disabled by GitHub
Actionactions-cool/maintain-one-comment, every tagSame campaign (SafeDep: 15 tags); repository disabled by GitHub
Action refissues-helper@v2.2.0 = 896379edfaac994ff214a3097c6c82e21be16066Resolved in the pro-components run (CyberXYZ, from the run log)
Action refissues-helper@v2.2.1 = a0c53dd42fc842d2f9276c5a1d4f9a26abe8713dSocket
Action refissues-helper@v3 = 147337a919d9Malicious tag commit (SafeDep)
Worm files in this campaign (SHA-256)
.claude/index.js26bc468790d3fa35c0e84423165fcf555bd4da6b5fd5f6d5b349262bd9f2b8aaPayload, 500,143 bytes
.claude/setup.mjs, .vscode/setup.mjs2be36c46d3f421bbd89bcb94997afb85020a672d82b64114637741411d7fc919Bun loader, same file twice
.claude/settings.json9566275be80af10b8c6be1d47bfcfdbe5590f472b1a9abd85bad24e0a9a7a891SessionStart hook: node .claude/setup.mjs. Re-infected branches carry a second hook in the same file
.vscode/tasks.json927387d0cfac1118df4b383decc2ea6ba49c9d2f98b47098bcbcba1efc026e1frunOn: folderOpen
The same files in the August keyv commit (SHA-256)
.claude/settings.json14eb4ce01dd4307759887ff819359b70d7d9ff709ecde039a5abc1aac325b128keyv build
.claude/setup.mjs, .vscode/setup.mjsfd3ca4007b225fdf8de7af4345a19179d5efa8c4bb9205f88cda806e5684b1ebkeyv build
.claude/math_init.js9fc2570b7cef51c1b8df116d144d11ff4096357be7d2c4c6367cfc2509cf1bcckeyv build payload
The June build on metersphere (SHA-256)
.github/setup.jsc48b77e30d72bc3295ec9736996834fe4927dcd499332e75d06c09884052a4e7Payload runner, 4,646,584 bytes. Re-wrapped on every run, so other copies hash differently
.cursor/rules/setup.mdccf9e7b24b756df81fbae37941cb41c8de6beaf848fafa332a10ea4490c4fd260Cursor rule that runs the payload (StepSecurity, SafeDep; our analysis)
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 serviceRemote-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 every branch, then the machine

The default branch tells you nothing here. List the worm's config files on every branch of a clone, without checking any branch out:

in a clone, bash or zsh: every branch, nothing checked outgit fetch origin '+refs/heads/*:refs/remotes/origin/*' git for-each-ref --format='%(refname:short)' refs/remotes/origin | while read -r b; do git ls-tree -r --name-only "$b" -- .claude .vscode .gemini .cursor .github/setup.js | sed "s|^|$b |" done

Any branch that lists .claude/settings.json, .gemini/settings.json, .vscode/tasks.json, .cursor/rules/ or .github/setup.js needs a look. Read them with git show, and look for a SessionStart hook or a folderOpen task that runs a script. If you find the worm and might have opened that branch, check the 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.

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 it yourself

Scan a checkout before you open it

Our CLI reads these files without running them. We ran the released version, 1.4.75 from PyPI, signed out and with uploads off, against a shallow clone of one infected alibaba/hooks branch, chore/node_version. We never opened that clone in an editor or agent, and we deleted it afterwards.

install, then scan a checkout without opening itpip install cyberxyz-scanner==1.4.75 xyz code-scan agents ./hooks --no-upload xyz code-scan ci ./hooks --no-upload

code-scan agents reads Claude Code and Gemini settings, VS Code folder-open tasks and agent instruction files. It matched all five worm files by hash and flagged both autorun entries:

xyz code-scan agents · alibaba/hooks branch chore/node_version · CLI 1.4.75 · 20 Oct 2026 · columns trimmedCRITICAL agent-known-worm-file .claude/index.js:1 CRITICAL agent-known-worm-file .claude/settings.json:1 CRITICAL agent-hook-command .claude/settings.json:9 CRITICAL agent-known-worm-file .claude/setup.mjs:1 CRITICAL agent-known-worm-file .vscode/setup.mjs:1 CRITICAL agent-known-worm-file .vscode/tasks.json:1 CRITICAL agent-folder-open-task .vscode/tasks.json:7 7 findings: 7 critical

Each finding says what to do. The first reads: “.claude/index.js is byte for byte a known Mini Shai-Hulud file (sha256 26bc468790d3fa35..., seen as .claude/index.js in ant-design/pro-components@1bc4049d). Do not open this checkout in an AI agent or editor.”

code-scan ci on the same branch named the hijacked action in all five places its issue workflows use it:

xyz code-scan ci · same branch · critical and high rows, columns trimmed; files in .github/workflows/; every critical row is uses: actions-cool/issues-helper@v3CRITICAL ci-compromised-action comment-when-needs-more-info.yml:13 CRITICAL ci-compromised-action issue-close-require.yml:12 CRITICAL ci-compromised-action issue-close-require.yml:19 CRITICAL ci-compromised-action issue-reply.yml:13 CRITICAL ci-compromised-action issue-reply.yml:26 HIGH ci-secrets-to-unpinned-action static.yml:66 32 findings: 5 critical, 1 high, 12 medium, 4 low, 10 info

Both commands exited 1. So did the thresholds our GitHub Action uses by default, --fail-on critical for the CI scan and --fail-on high for the agents scan. CyberXYZSecurity/depalert-action v1.3.0 runs both scans next to its dependency gate. Pin it by commit like any other action:

.github/workflows, CyberXYZ DepAlert pinned to v1.3.0- uses: CyberXYZSecurity/depalert-action@f5f42ed8b7e8128614ff1655a3d64ea0b3231c9c # v1.3.0 with: api-key: ${{ secrets.XYZ_API_KEY }}

What it does not do:

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
ExfiltrationT1567.001Exfiltration to Code Repository
ImpactT1485Data Destruction

References

  1. CyberXYZGH Archive hunt (1 Aug to 13 Oct 2026), workflow sweep, tokenless re-checks and CLI runs (20 October 2026, 18:30 to 18:37 UTC)
  2. CyberXYZMini Shai-Hulud in ant-design/pro-components: six branches in seven seconds (15 Oct 2026)
  3. CorgeaMini Shai-Hulud npm worm: the AntV wave (May 2026)
  4. StepSecurityMiasma npm Supply Chain Attack: Self-Spreading Worm via Phantom Gyp (3 Jun 2026)
  5. SafeDepMiasma Worm Targets AI Coding Agents via GitHub Repos (5 Jun 2026)
  6. SafeDepMiasma Worm: Most Infected GitHub Repos Are Still Live (11 Jun 2026)
  7. Aikidoant-design/pro-components issue #9704: This repository is compromised (24 Sep 2026)
  8. SafeDepMini Shai-Hulud Is Still Infecting GitHub Repositories (24 Sep 2026)
  9. SocketRe-Enabled GitHub Actions Expose Thousands of Repositories to Mini Shai-Hulud (24 Sep 2026, updated 25 Sep)
  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.