目录
在 Miasma 蠕虫 将一个 4.3 MB 的凭证窃取程序植入公共 GitHub 仓库八天后——以及该蠕虫锁定的维护者夺回账户三天后——大多数受感染仓库仍在加载有效载荷。SafeDep 于 6 月 5 日发布了受害者名单并于 6 月 11 日运行了新一轮 GitHub 代码搜索。在这两个时间点之间,横跨 56 个账户的 123 个仓库仍在 665 个分支上运行活动状态的 dropper。任何克隆这些仓库并在 VS Code、Cursor、Claude Code 或 Gemini 中打开它的人,都会在无需进一步交互的情况下运行凭证收集器。
几乎所有被清理的仓库背后都有安全团队支持。微软的 Azure/durabletask 和 Azure-Samples/llm-fine-tuning 是干净的。icflorescu 的全部五个仓库、所有 jagreehal 以及 jahirfiquitiva/Frames 也都是干净的。继续传播的是那些没有安全组织监控其仓库的个人开发者、学生和小型团队。尼日利亚银行的企业网站、去中心化金融交易所的交易协议、大学课程的学生作业,以及数十个个人项目,此刻仍在提供恶意软件。
我们的测量方法
检测方法是 icflorescu 在蠕虫攻击后 发布的:不要信任提交作者,应在每个分支上检查有效载荷文件。dropper 位于 .github/setup.js,单行约 4.3 MB。没有合法的仓库会携带这个文件。
扫描过程从不检出工作树,也从不执行任何代码。只需进行一次无 blob 的 --no-checkout 克隆加上每个分支的对象存在性测试就足够了。
# 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两份数据集驱动了本次运行。第一份是 SafeDep 于 6 月 5 日发布的 123 个仓库列表,这些仓库在披露时已携带 node .github/setup.js 钩子。第二份是 6 月 11 日对相同调用字符串进行的新一轮 GitHub 代码搜索,发现了 58 个候选仓库。其中 41 个不在原始发布列表上。随后对每个结果在所有分支上进行 git 扫描,以确认有效载荷存在并记录每个被污染的提交位于哪个分支。
这两个数字都存在低估。GitHub 代码搜索仅索引默认分支,并跳过大于约 384 KB 的文件,因此 4.3 MB 的 setup.js 本身从未被索引。小型启动器配置是这一切可被搜索的唯一原因,而蠕虫隐藏在侧分支上的习惯使得大多数有效载荷完全超出代码搜索范围。
发布列表中 70% 的仓库仍被感染
6 月 5 日发布的 123 个仓库,截至 6 月 11 日的状态分布如下。
| 结果(6 月 11 日) | 仓库数 | 占比 |
|---|---|---|
| 仍被感染,有效载荷活动 | 86 | 70% |
| 已清理,仓库仍在线 | 21 | 17% |
| 已禁用、删除或设为私有 | 16 | 13% |
在公开名单公布这些仓库六天后——在此活动因击中 73 个微软仓库 而登上国际新闻之后——七成的仓库仍在提供活动有效载荷。21 个完成清理的几乎都是有安全团队支持的名字:Azure/durabletask、Azure-Samples/llm-fine-tuning、icflorescu 和 jagreehal 以及 jahirfiquitiva 仓库。16 个消失的仓库是被 GitHub 或其所有者下线的。其他一切均未受影响。
仍在提供有效载荷的是谁
截至 6 月 11 日经确认仍被感染的完整仓库列表,以及每个仓库的中毒分支数量和有效载荷大小:
miasma-still-infected-repos.csv
| 仓库 | 所有者 | 中毒分支数 | 有效载荷字节数 | |
|---|---|---|---|---|
| 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 行
| 4 列 |
受害者按账户聚集,因为蠕虫会遍历被盗令牌可访问的每个仓库。rhemlock7 有 9 个仓库仍被感染,nasher721 有 9 个,jedsada-gh 有 8 个,beatrizamante 有 5 个。一组被盗凭证会污染整个个人账户。
受影响者的分类样本(非按 GitHub stars 分类):
- 一家银行。
Summit-Bank-Limited/corporate-website在 12 个分支上携带有效载荷,提交以地址[email protected]伪造。 - 一个去中心化金融交易所。
PositionExchange/position-protocol和三个同级仓库处于活动状态,协议仓库在 27 个分支上被污染,提交伪造为[email protected]。 - 一门大学课程。四个
dcc-cc3002/citric-liquid-*仓库是智利大学软件设计课程的学生作业,每个在 8 到 9 个分支上被污染,提交以学生的@ug.uchile.cl地址伪造。 - 一个毕业项目团队。
KSU-Quantum-Capstone/CS4850-DL1,位于肯尼索州立大学。 - 一个基础设施项目。
metersphere/helm-chart,MeterSphere 测试平台的 Helm chart,提交伪造为其真正的@fit2cloud.com维护者。
有效载荷大小从 4.36 MB 到 5.35 MB 不等,约有 38 种不同大小。dropper 针对每个受害者重新编译,包含轮换的 AES 密钥和凯撒移位,因此文件哈希会变化但架构保持不变。只有小型启动器配置保持不变,扫描正是匹配这些。
提交是以维护者自己的名义伪造的
这些感染在侧分支上存活的原因是它们被设计成不被注意。SafeDep 最初的分析描述了 icflorescu 的 main 分支上的 github-actions <[email protected]> 作者。那是响亮的一半。在本次扫描记录的 808 个引入有效载荷的提交中,除三个外所有提交都是用真实维护者的姓名和邮箱伪造的,而非机器人的。每个提交都带有 [skip ci] 以抑制通知,提交日期被回溯到 2013 年到 2026 年之间的时间跨度,使每个被污染的提交融入该分支自身的历史。
# 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:trueangular-indonesia/starter-angular-loopback-bulma 在提交 57ae9502 中携带其有效载荷,伪造为 Julian GM Alimin <[email protected]> 并回溯到 2017 年。该组织的 GitHub Pages 站点 angular-indonesia.github.io 以同样的方式被污染,伪造为维护者 Irfan Maulana <[email protected]>。MeterSphere 提交保留了他们冒充的工程师原始的中文提交信息。伪造针对每个仓库和每个分支进行了调整。
如果您审计自己的仓库,不要通过提交作者来检测。这通过了所有"这是机器人吗?"检查。通过有效载荷文件来检测。
仅清理 main 分支是不够的
最清晰的说明是 metersphere/helm-chart。其默认分支 v3.x 于 6 月 8 日被标题为 Remove Miasma config injection indicators 的提交清理。维护者知情并已修复。然而在 6 月 11 日,4.6 MB 的有效载荷仍在其他三个分支 fix/pvc、pr@main@build_redis 和 refactor/kafka 上活动。
这就是大多数仓库保持感染的方式。维护者在 main 上看到提交,回退或重置它,并认为工作完成了。蠕虫已在每个可访问的分支上植入了相同的有效载荷,每个都在看似合理的回溯提交下,而那些分支从未被扫描。在发布列表中 86 个仍被感染的仓库中,许多有一个看起来已清理的默认分支,而在相邻分支上有一个活动有效载荷。
根本原因:一个超越一切的凭证
入口向量现已从 GitHub 自己的日志中确认,来源是 icflorescu 的后续。提交是通过他的 GitHub CLI OAuth 令牌进行的,该令牌创建于 1 月 17 日,五个月后从微软 Azure IP 范围内通过 GitHub API 使用。该凭证是一个令牌,而非他的密码或双因素认证,这就是为什么两者都无法阻止它。
一个结构性细节解释了一个盗窃为何能遍历如此多的账户。GitHub 在一个 OAuth 应用授权下堆叠了许多访问令牌,每台机器和每次重新登录各一个。撤销单个令牌、重新认证,甚至 GitHub 自己自动驱逐旧令牌,都不会撤销授权。只有撤销整个授权才能杀死其下的所有令牌。icflorescu 在 5 月轮换了令牌并重建了两台笔记本电脑,但 1 月的令牌在 6 月仍然有效,因为底层的授权从未被撤销。该令牌最可能是通过 TanStack 供应链攻击更早被收割的,在某些常规的 npm install 期间拉取了被感染的依赖项,并在数周后重复使用。
这就是 Shai-Hulud 循环。一个被污染的依赖项收割了一个写作用域凭证,蠕虫用它将有效载荷推送到凭证可访问的每个仓库,而有效载荷在开发者于 AI 编辑器中打开任何这些仓库时收割更多凭证。
该技术改进了较旧的方法。2025 年 8 月对 Nx 构建系统的 s1ngularity 攻击 compromising 2,180 个 GitHub 账户并暴露了 7,200 个仓库,将被盗数据泄露到名为 s1ngularity-repository 的公共仓库。那款恶意软件使用开发者自己安装的 Claude、Q 和 Gemini CLI 来枚举和收割密钥。在 s1ngularity 使用 AI 代理收割凭证的地方,Miasma 植入了使这些相同代理首先运行窃取程序的配置文件。
如何检查您自己的仓库
这些项目的已发布 npm 包是干净的。在此次攻击中,mantine-datatable、AntV 包或其他任何包都没有向注册表发布过恶意版本。危险完全是本地的,且在 npm uninstall 后仍然存在。如果您克隆了在 Shai-Hulud 或 Miasma 活动期间活跃的任何仓库:
- 不要在 VS Code、Cursor、Claude Code 或 Gemini 中打开工作副本,不要运行
npm test。无检出的克隆可将有效载荷保留在磁盘之外。 - 使用本文顶部的命令扫描每个分支以查找
.github/setup.js。通过有效载荷文件检测,而非通过提交作者。 - 如果找到它,将受影响的分支重置到被污染提交的前一个提交,而非
git revert,后者会在旧 SHA 处留下可检索的 dropper。然后请求 GitHub 垃圾回收孤立的提交。 - 撤销您怀疑的任何 CLI 的 OAuth 应用授权,而不仅仅是其令牌。撤销令牌会使授权保持活动状态。
- 在每次差异和您克隆的每个仓库中,将
.claude/、.gemini/、.cursor/和.vscode/文件视为可执行代码。它们在文件夹打开和会话开始时自动运行命令,大多数审查工作流都会忽略它们。
蠕虫赌的是克隆源代码感觉是安全的。AI 编码代理和 IDE 自动运行功能改变了这一点,而从 123 个仓库八天后仍活动这一证据来看,大多数生态系统尚未跟上。
相关阅读:Miasma 蠕虫通过 GitHub 仓库针对 AI 编码代理(原始 source-repo 分析),迷你 Shai-Hulud"Miasma"袭击 @redhat-cloud-services(分阶段的 Bun 加载器,已逆向),以及 nx 构建系统妥协(s1ngularity 先例)。
- github
- malware
- supply-chain
- shai-hulud
- ai-coding-agents
SafeDep 博客的最新内容
关注以获取开源安全与工程的最新更新和见解