Miasma Worm: Most Infected GitHub Repos Are Still Live

SafeDep Team
9 min read

Table of Contents

Eight days after the Miasma worm forged a 4.3 MB credential stealer into public GitHub repositories and three days after the maintainer it locked out got his account back, most of the infected repos are still serving the payload. SafeDep re-scanned the victim list published on June 5 and ran a fresh GitHub code-search sweep on June 11. Across both, 123 repositories spanning 56 accounts still carry the live dropper on 665 branches. Anyone who clones one of them and opens it in VS Code, Cursor, Claude Code, or Gemini runs the credential harvester with no further interaction.

The repos that got cleaned almost all had a security team behind them. Microsoft’s Azure/durabletask and Azure-Samples/llm-fine-tuning are clean. So are all five of icflorescu’s repos, all of jagreehal, and jahirfiquitiva/Frames. What stays live is the long tail of individual developers, students, and small teams who do not have a security org watching their repos. A Nigerian bank’s corporate site, a DeFi exchange’s trading protocol, a university course’s student submissions, and dozens of personal projects are still serving malware right now.

What we measured

The detection is the one icflorescu published after the worm hit him: do not trust the commit author, check for the payload file across every branch. The dropper lives at .github/setup.js, a single line of roughly 4.3 MB. No legitimate repository ships that file.

The scan never checks out a working tree and never executes anything. A blobless --no-checkout clone plus a per-branch object existence test is enough.

Terminal window
# Defensive scan. Nothing is checked out, nothing runs.
git clone --no-checkout --filter=blob:none https://github.com/<owner>/<repo>.git probe
cd probe
git fetch origin '+refs/heads/*:refs/remotes/origin/*'
for b in $(git for-each-ref --format='%(refname:short)' refs/remotes/origin | sed 's#origin/##'); do
git cat-file -e "origin/$b:.github/setup.js" 2>/dev/null && echo "INFECTED: $b"
done

Two datasets fed the run. The first is the 123 repositories SafeDep published on June 5 as carrying the node .github/setup.js hook at disclosure time. The second is a fresh GitHub code search for that same invocation string, run on June 11, which surfaced 58 candidate repositories. 41 of those 58 were not on the original published list. Every result was then git-scanned across all branches to confirm the payload is present and to record which branch each poisoned commit sits on.

Both numbers undercount. GitHub code search only indexes default branches and skips files larger than roughly 384 KB, so the 4.3 MB setup.js itself never indexes. The small launcher configs are the only reason any of this is searchable, and the worm’s habit of hiding on side branches puts most of the payload beyond code search entirely.

Seventy percent of the published list is still infected

Of the 123 repositories published on June 5, the state on June 11 breaks down as follows.

Outcome (June 11)ReposShare
Still infected, payload live8670%
Cleaned, repo still up2117%
Disabled, deleted, or made private1613%

Six days after a public list named these repositories, and after the campaign made international news for hitting 73 Microsoft repositories, seven in ten were still serving the live payload. The 21 that cleaned up are almost entirely the names with a security team behind them: Azure/durabletask, Azure-Samples/llm-fine-tuning, the icflorescu and jagreehal and jahirfiquitiva repos. The 16 that vanished were taken down by GitHub or their owners. Everything else is untouched.

Who is still serving the payload

The full list of repositories confirmed still-infected on June 11, with the number of poisoned branches and the payload size per repo:

miasma-still-infected-repos.csv
repo owner infected_branches payload_bytes
1 Abner97/InvitationsApi Abner97 3 4634669
2 Abner97/tournaments-app Abner97 2 4634669
3 Agentic-Insights/dreamgen Agentic-Insights 10 4646419
4 Agentic-Insights/foundry Agentic-Insights 2 4646419
5 Agreon/budgie Agreon 12 4634464
6 Agreon/styco Agreon 6 4634464
7 aiyeola/nextjs-blog aiyeola 2 4477023
8 aiyeola/scrape aiyeola 2 4477023
9 A-Mitch/learningRoR A-Mitch 2 4471909
10 A-Mitch/spotify-codes-simulation A-Mitch 2 4471909
11 anasdevv/customer-portal anasdevv 2 4361318
12 anasdevv/hotel-management-backend anasdevv 6 4448127
13 anasdevv/my-calendar anasdevv 2 4361318
14 anasdevv/reservation-system anasdevv 3 4361318
15 angular-indonesia/angular-indonesia.github.io angular-indonesia 2 4634519
16 angular-indonesia/starter-angular-loopback-bulma angular-indonesia 11 4634519
17 baltruschat/jira-bug-tracker baltruschat 4 4634733
18 beatrizamante/facial-recognition-api beatrizamante 2 4635011
19 beatrizamante/interactive-fiction-reviewer beatrizamante 4 4635011
20 beatrizamante/utfpr_classlog beatrizamante 8 4635011
21 beatrizamante/utfpr_classlog_frontend beatrizamante 8 4635011
22 beatrizamante/watchme_frontend beatrizamante 4 4635011
23 bhagyamudgal/cuju-web bhagyamudgal 2 4377645
24 bhagyamudgal/notisol bhagyamudgal 2 4377645
25 bhagyamudgal/worktree-cli bhagyamudgal 3 4377645
26 bitzquad/bitzquad.com bitzquad 1
27 bitzquad/bitzquad.com-2.0 bitzquad 1
28 bitzquad/nebula-docs bitzquad 1
29 braune-digital/bd-php-to-ts-converter-bundle braune-digital 2 4634733
30 braune-digital/BrauneDigitalImagineBundle braune-digital 4 4634733
31 Code-Web-Basic/CompilerGo Code-Web-Basic 5 4634925
32 CurryMessi/tiktok-video CurryMessi 2 4635243
33 czech-sfl/konference czech-sfl 2 4634914
34 dandycheung/Frames dandycheung 1 4559058
35 dcc-cc3002/citric-liquid-Benjjvv dcc-cc3002 8 4619268
36 dcc-cc3002/citric-liquid-cpereiram dcc-cc3002 9 4619268
37 dcc-cc3002/citric-liquid-ihumire dcc-cc3002 9 4619268
38 dcc-cc3002/citric-liquid-Jarinx dcc-cc3002 9 4619268
39 dean-s-list/business-visa-server dean-s-list 2 4377645
40 dean-s-list/deanslist-platform dean-s-list 2 4647225
41 dean-s-list/deanslist-services dean-s-list 2 4377645
42 dsrikant/smarcart dsrikant 4 4620322
43 dzhu8/dzhu.github.io dzhu8 2 4634066
44 EdsonVillarroel/e-commerce EdsonVillarroel 6 4635467
45 EdsonVillarroel/portfolio EdsonVillarroel 11 4635467
46 EdsonVillarroel/vision-kit EdsonVillarroel 2 4635467
47 erbieio/erbie erbieio 15 4390381
48 Factlink/js-library Factlink 3 4548618
49 Factlink/url_normalizer Factlink 2 4548618
50 GNF-Labs/millenium-lms-web-app GNF-Labs 4 4463853
51 jchable/gpx-utility-analyzer jchable 2 4634567
52 jedsada-gh/ApiMovie-UP jedsada-gh 3 4645945
53 jedsada-gh/blockchain-playground jedsada-gh 2 4634301
54 jedsada-gh/co-work-admin jedsada-gh 4 4634301
55 jedsada-gh/co-work-android jedsada-gh 3 4634301
56 jedsada-gh/co-work-api jedsada-gh 3 4634301
57 jedsada-gh/co-work-katalon jedsada-gh 2 4634301
58 jedsada-gh/co-work-provider jedsada-gh 3 4634301
59 jedsada-gh/node-js-netpie jedsada-gh 9 4634301
60 jgutierrezdtt/skills-hello-github-actions jgutierrezdtt 2 4643795
61 jgutierrezdtt/Sports-Center jgutierrezdtt 10 4643795
62 jgutierrezdtt/Vulndemo jgutierrezdtt 3 4643795
63 killerapp/mermaid-render killerapp 4 4646419
64 KSU-Quantum-Capstone/CS4850-DL1 KSU-Quantum-Capstone 4 4630939
65 kylezap/ctrl-alt-win kylezap 21 4645618
66 kylezap/kylezapcicdotcom kylezap 2 4377461
67 kylezap/rightsize-meals kylezap 21 4645618
68 kylezap/tree-view kylezap 2 4377461
69 leanderloew/explainability-simulation leanderloew 2 4633915
70 lucasconnellm/openclaw-fluxer lucasconnellm 4 4633825
71 messismore/Digitale-Ausstellung messismore 4 4634208
72 messismore/Studio-Grotto messismore 4 4634208
73 metersphere/helm-chart metersphere 3 4646584
74 mhar-andal/MyBlok mhar-andal 6 4508470
75 mhar-andal/stock-forum-ethereum mhar-andal 2 4508470
76 mmlngl/contacttracing.app-graphql-api mmlngl 3 4647729
77 mmlngl/flua-launch mmlngl 3 4647729
78 nasher721/3dgenerator nasher721 2 4634138
79 nasher721/AnkiFellowCollab nasher721 8 4634138
80 nasher721/Extract721 nasher721 3 4646453
81 nasher721/Medical-OCR nasher721 13 4646453
82 nasher721/note-clarity nasher721 4 4634138
83 nasher721/remix-of-remix-of-round-robin-notes nasher721 20 4646453
84 nasher721/remix-of-round-robin-notes nasher721 6 4634138
85 nasher721/scheduler nasher721 21 4646453
86 nasher721/textcleaner nasher721 3 4634138
87 neilfarmer/k8s-health neilfarmer 9 4634793
88 neilfarmer/platform-spec neilfarmer 8 4634793
89 Netpoc/company Netpoc 2 4646713
90 nodejs-indonesia/blogs nodejs-indonesia 2 4634519
91 nodejs-indonesia/starter-loopback-fireloop nodejs-indonesia 14 4645302
92 otaviosoaresp/what-the-fork otaviosoaresp 2 4622883
93 paulmojicatech/angular-dc-meetup-may-24-2022 paulmojicatech 4 4634921
94 paulmojicatech/pmt paulmojicatech 6 4634921
95 paulmojicatech/wonder-worm paulmojicatech 2 4634921
96 PositionExchange/decentralized-perpetual-trading-protocol-cross-chain PositionExchange 27 4645446
97 PositionExchange/dptp-client-sdk PositionExchange 3 4648910
98 PositionExchange/evm-matching-engine PositionExchange 4 4648910
99 PositionExchange/position-protocol PositionExchange 27 5351744
100 Pouleyy/nodeAirBnB Pouleyy 2 4635440
101 rhemlock7/ecommerce-back-end rhemlock7 4 4646471
102 rhemlock7/express-note-taker rhemlock7 7 4646471
103 rhemlock7/minimalist-portfolio-mkii rhemlock7 9 4646471
104 rhemlock7/NoSQL-Social-Network rhemlock7 5 4646471
105 rhemlock7/SQL-Employee-Tracker rhemlock7 6 4646471
106 rhemlock7/svg-logo-maker rhemlock7 6 4646471
107 rhemlock7/TeamSync-Client rhemlock7 3 4646471
108 rhemlock7/TeamSync-KanBan rhemlock7 8 4646471
109 rhemlock7/weather-app-api rhemlock7 9 4646471
110 rudy-marquez/WebGoatNet rudy-marquez 3 4634940
111 Saul9201/daily-trends Saul9201 2 4645778
112 Saul9201/hello-circleci Saul9201 2 4376490
113 Skipperlla/rn-swiper-list Skipperlla 8 4611776
114 Slickteam/hubspot-java Slickteam 4 4549341
115 snoopyrain/rails102 snoopyrain 9 4635175
116 Summit-Bank-Limited/corporate-website Summit-Bank-Limited 12 4646713
117 tumolaha/lerning-setup tumolaha 2 4646776
118 Weasledorf-Inc/taskmaster Weasledorf-Inc 7 4634793
119 WilChrist/SecurityAuditApp WilChrist 2 4549461
120 WilChrist/WilCovEst WilChrist 2 4549461
121 wormholes-org/wormholes-client wormholes-org 5 4650163
122 Zaynex/13f-vis Zaynex 9 4549400
123 Zaynex/x-atm Zaynex 3 4549400
123 rows
| 4 columns

The victims cluster by account, because the worm walks every repository a stolen token can reach. rhemlock7 has 9 repos still infected, nasher721 has 9, jedsada-gh has 8, beatrizamante has 5. One harvested credential poisons an entire personal account.

A sample of who is affected, by category rather than by GitHub stars:

  • A bank. Summit-Bank-Limited/corporate-website carries the payload on 12 branches, with commits forged under the address [email protected].
  • A DeFi exchange. PositionExchange/position-protocol and three sibling repos are live, the protocol repo poisoned across 27 branches, commits forged as [email protected].
  • A university course. Four dcc-cc3002/citric-liquid-* repos are student submissions for a software design course at the University of Chile, each poisoned across 8 to 9 branches with commits forged under students’ @ug.uchile.cl addresses.
  • A capstone team. KSU-Quantum-Capstone/CS4850-DL1 at Kennesaw State.
  • An infrastructure project. metersphere/helm-chart, the Helm chart for the MeterSphere testing platform, with commits forged under its real @fit2cloud.com maintainers.

Payload sizes range from 4.36 MB to 5.35 MB across roughly 38 distinct sizes. The dropper is recompiled per victim, with rotated AES keys and Caesar shift, so the file hash changes while the architecture stays constant. Only the small launcher configs stay the same, and the scan matches on those.

The commits are forged under the maintainer’s own name

The reason these infections survive on the side branches is that they are built not to be noticed. SafeDep’s original analysis described the github-actions <[email protected]> author on icflorescu’s main branches. That was the loud half. Across the 808 payload-introducing commits catalogued in this scan, all but three are forged under the real maintainer’s own name and email, not the bot’s. Every single one carries [skip ci] to suppress notifications, and the commit dates are backdated across a span from 2013 to 2026 so each poisoned commit blends into that branch’s own history.

Terminal window
# PositionExchange/position-protocol, develop branch
# https://github.com/PositionExchange/position-protocol/commit/ce456191d71c3399c114b3d539666477df1322bc
author: Justin <[email protected]>
date: 2022-11-14T15:51:31Z # backdated ~3.5 years
verified: false # unsigned
message: Update README.md [skip ci] [skip ci] [skip ci] [skip ci] skip-checks:true

angular-indonesia/starter-angular-loopback-bulma carries its payload in commit 57ae9502, forged under Julian GM Alimin <[email protected]> and backdated to 2017. The org’s GitHub Pages site angular-indonesia.github.io is poisoned the same way, under maintainer Irfan Maulana <[email protected]>. The MeterSphere commits preserve the original Chinese-language messages of the engineers they impersonate. The forgery is tuned to each repo and each branch.

If you audit your own repos, do not detect this by commit author. A backdated commit forged under your own name passes every “is this the bot?” check. Detect by the payload file.

Cleaning main is not enough

The clearest illustration is metersphere/helm-chart. Its default branch v3.x was cleaned on June 8 by a commit titled Remove Miasma config injection indicators. The maintainers knew and fixed it. Yet on June 11 the 4.6 MB payload is still live on three other branches, fix/pvc, pr@main@build_redis, and refactor/kafka.

This is how most of them stay infected. A maintainer sees the commit on main, reverts or resets it, and considers the job done. The worm planted the same payload on every branch it could reach, each under a plausible backdated commit, and those branches never get scanned. Of the 86 still-infected repos from the published list, many have a cleaned-looking default branch and a live payload one branch over.

Root cause: a credential that outlived everything

The entry vector is now confirmed, from GitHub’s own logs, in icflorescu’s follow-up. The commits were made with his GitHub CLI OAuth token, created on January 17 and used five months later from a Microsoft Azure IP range through the GitHub API. The credential was a token, not his password or his 2FA, which is why neither stopped it.

One structural detail explains why a single theft could walk so many accounts. GitHub stacks many access tokens under one OAuth app authorization, one per machine and per re-login. Revoking an individual token, re-authenticating, even GitHub’s own automatic eviction of old tokens, none of it revokes the grant. Only revoking the whole authorization kills every token under it. icflorescu rotated tokens and rebuilt both his laptops from scratch in May, and the January token still worked in June because the authorization underneath was never revoked. The token was most likely harvested earlier through the TanStack supply-chain wave, a compromised dependency pulled during some routine npm install and reused weeks later.

That is the Shai-Hulud loop. A poisoned dependency harvests a write-scoped credential, the worm uses it to push the payload into every repository the credential can reach, and the payload harvests more credentials when a developer opens any of those repos in an AI editor.

The technique refines an older one. The s1ngularity attack on the Nx build system in August 2025 compromised 2,180 GitHub accounts and exposed 7,200 repositories, exfiltrating stolen data to public repos named s1ngularity-repository. That malware used the developer’s own installed Claude, Q, and Gemini CLIs to enumerate and harvest secrets. Where s1ngularity used AI agents to harvest credentials, Miasma plants the config files that make those same agents run the stealer in the first place.

How to check your own repos

The published npm packages for these projects are clean. No malicious version of mantine-datatable, the AntV packages, or the others was ever shipped to a registry in this arm. The danger is entirely local and it survives npm uninstall. If you have cloned any repository that was active during a Shai-Hulud or Miasma wave:

  • Do not open the working copy in VS Code, Cursor, Claude Code, or Gemini, and do not run npm test. A no-checkout clone keeps the payload off your disk.
  • Scan every branch for .github/setup.js using the command at the top of this post. Detect by the payload file, never by the commit author.
  • If you find it, reset the affected branches to the parent of the poisoned commit rather than git revert, which leaves the dropper retrievable at the old SHA. Then ask GitHub to garbage-collect the orphaned objects.
  • Revoke the OAuth app authorization for any CLI you suspect, not just its tokens. Revoking a token leaves the grant alive.
  • Treat .claude/, .gemini/, .cursor/, and .vscode/ files as executable code in every diff and every repo you clone. They auto-run commands on folder-open and session-start, and most review workflows ignore them.

The worm’s bet is that cloning source code feels safe. AI coding agents and IDE auto-run features changed that, and on the evidence of 123 repos still live eight days in, most of the ecosystem has not caught up.

Related reading: Miasma worm targets AI coding agents via GitHub repos (the original source-repo analysis), Mini Shai-Hulud “Miasma” hits @redhat-cloud-services (the staged Bun loader, reversed), and the nx build system compromise (the s1ngularity precedent).

  • github
  • malware
  • supply-chain
  • shai-hulud
  • ai-coding-agents

Author

SafeDep Logo

SafeDep Team

safedep.io

Share

The Latest from SafeDep blogs

Follow for the latest updates and insights on open source security & engineering

Background
SafeDep Logo

Ship Code.

Not Malware.

Start free with open source tools on your machine. Scale to a unified platform for your organization.