这个仓库真的是 Microsoft 的
2026 年 6 月 5 日至 6 日,Miasma 蠕虫把窃取凭据的代码推进了 Microsoft 自己的四个 GitHub 组织——Azure、Azure-Samples、microsoft、MicrosoftDocs——下的 73 个仓库里,其中包括官方部署 Action 的 Azure/functions-action,以及 durabletask 这个早在 5 月就已经被清理过一次的仓库。这一次,载荷没有等到 npm install。当一名开发者在 Claude Code、Cursor、Gemini CLI 或 VS Code 里打开这个仓库的那一刻,它就触发了。这篇文章讲的是为什么信任信号——「它是 Microsoft 的仓库」——又一次成了攻击面,以及当打开它的代理运行在一个按配置文件隔离的 Bromure VM 里、在一个凭据代理、一道读写护栏和一段包冷却期之后时,什么会发生改变。
一周前,这个蠕虫乘着 Red Hat 的 npm scope 而来,通过一个
preinstall 钩子触发。这一周,它乘着 Microsoft 的 GitHub
而来,根本不需要任何安装。Miasma 把项目级配置植入了 73 个
归 Microsoft 所有的仓库——其中就有官方部署 Action
Azure/functions-action——而当一名开发者在 Claude Code、
Cursor、Gemini CLI 或 VS Code 里打开这个仓库的那一刻,载荷
就执行了。触发点不是克隆,而是在你的代理里打开它。
一名为了调试一次失败的部署而克隆 Azure/functions-action 的开发者
不会迟疑,他指向这个仓库的代理也一样。它是来自 Microsoft 自己的
Azure 组织的第一方仓库——半个生态系统都以 Azure/functions-action@v1
引用的那个 GitHub Action 的规范源头。没有维护者需要审查,因为维护者
就是 Microsoft。没有名字需要眯着眼去看,因为名字分毫不差就是它该有
的样子。于是仓库被打开了,代理像每一个现代编码工具在打开文件夹时
所做的那样读取项目的配置——而其中一个配置文件指向一条命令,这条
命令是一个约 4.3 兆字节的 blob,一上来就开始读取文件系统找钥匙。
我们七天前写过这同一场活动的 npm 那一半。Miasma 是同一颗蠕虫 ——是 TeamPCP 在 5 月中旬公开发布的 那段「Mini Shai-Hulud」代码的一个变种——而它所演示的那个让人不舒服 的事实也是同一个,只是再拧了一圈:信誉良好的来源不是一种安全控制。 它感觉像是。大多数供应链建议都倚仗着它。而在 6 月 5 日,它什么都没 换到,而且是两重意义上的什么都没换到:命名空间是 Microsoft 的,而 执行并非来自你选择安装的某个包。它来自打开一个文件夹。
Miasma 对 Microsoft 的仓库做了什么。
2026 年 6 月 5 日至 6 日,在恶意提交被推进 Microsoft 的仓库之后, GitHub 禁用了 Microsoft 四个 GitHub 组织下的 73 个仓库——这一点 见 The Hacker News 以及 StepSecurity 的详细拆解。Redmond Magazine 在 6 月 8 日做了报道。具体分布如下:
- Azure —— 49 个仓库,其中包括
Azure/functions-action(官方的 Functions 部署 Action),以及 .NET、Python、Java、Go 和 PowerShell 的语言 worker。 - microsoft —— 10 个仓库。
- Azure-Samples —— 13 个仓库。
- MicrosoftDocs —— 1 个仓库。
剥到机制层面的轮廓:
- 切入点是一个此前被攻陷的、拥有提交权限的贡献者账号,被用来
把恶意提交直接推进这些仓库——并打上
[skip ci]标记,让这些改动 绕过本来会运行的 CI/CD 检查。 - 这次提交植入了项目级配置——也就是当你打开文件夹时,一个编码 代理或 IDE 会自动读取并据以执行的那类文件:一个编辑器任务、一个 代理钩子、一个项目定义的 MCP 服务器。这正是 Adversa AI 的 TrustFall 在 Claude Code、Cursor CLI、Gemini CLI 和 Copilot CLI 上演示过的 同一类信任边界——这四者都会在文件夹信任提示之后立即执行项目定义 的配置。
- 载荷——约 4.3 MB 的混淆代码——会在仓库被在 Claude Code、
Gemini CLI、Cursor 或 VS Code 里打开时执行,或在通过一个
npm test脚本运行时执行。不是仅凭克隆。 把你的代理指向那棵 被克隆下来的目录树,才是运行它的动作。 - 一旦执行,它就在主机上搜刮 GitHub 令牌、AWS 密钥、Azure 服务
主体、GCP 凭据、npm 和 PyPI 发布令牌、SSH 密钥,以及
.env文件,然后用窃取到的访问权限把自己提交到下一处——正是这一点 让它成了一颗蠕虫,而不是一次性的攻击。
有一个细节值得多停留一下:Azure/durabletask 也在中招的仓库之列
——而它在 5 月的 TeamPCP 活动中就已经被攻陷过一次并被清理。一个
修复过一次的仓库,在五周之后又被重新投毒。清理不是一个你达成之后
就能保住的状态;它是一个状态,链条里另一个凭据一旦被夺走,你就会
从中跌落出去。
同样值得精确说清楚的,是哪些事没有发生。Microsoft 的企业网络
没有被攻破。Azure 这个云服务没有被攻破。没有任何客户数据、没有任何
生产系统被触及。这是一次针对源码仓库的攻击——而它波及面最广的
后果跟恶意软件本身毫无关系:在 GitHub 禁用 Azure/functions-action
的那一瞬间,地球上每一条引用了 Azure/functions-action@v1 的流水线
都解析不出来了。Microsoft 是最高调的携带者。真正被攻陷的,是那些
在 6 月 3 日到 5 日之间在代理里打开了被投毒仓库、凭据被从自己机器上
搜刮走的开发者。
同一个仓库,在 Bromure Agentic Coding 里面打开。
Bromure Agentic Coding 让你的编码代理运行在一个
按配置文件隔离的 Linux VM 里——它有自己的内核、自己的文件系统、
自己的网络栈,跑在 Apple 的 Virtualization 框架之上。一个配置文件
就是一个连贯的工作范围:这个客户、这个内部产品、这个你为了
调试部署而克隆下来的开源库。你把 Azure/functions-action 克隆进
那个配置文件,并在里面用你的代理打开它。打开文件夹的触发点完全
照设计触发。载荷运行起来。然后它跑去一台它够不到的主机上找钥匙。
因为凭据不在配置文件里。这个 VM 出厂自带存根——对 git、gh、
aws、kubectl、npm 以及任何期待一个 Authorization 头的东西
来说看起来都像真的假令牌。你的 Mac 上的一个代理坐在每一条离开
沙箱的连接前面,识别出那个存根,并在请求离开时在线路上把它换成
真正的密钥(那个握着钥匙的沙箱
详细讲了这套机制)。真正的 GitHub PAT、真正的 AWS 密钥、真正的
Azure 主体——它们没有一个会触及 VM 能读到的某个文件、某个环境变量
或某一页内存。SSH 密钥根本不会离开 macOS 钥匙串;只有 ssh-agent
套接字被转发进去,正如 OpenSSH 一向所设想的那样。
那么把 Miasma 的搜刮放进那道边界里走一遍。载荷在环境里找
$GITHUB_TOKEN,找到一个存根。它读 ~/.aws,什么都找不到。它读
~/.npmrc,找不到发布令牌。它读 ~/.ssh,找不到密钥文件——那里有
一个被转发的套接字,而不是磁盘上的一个私钥。那个 4.3 MB 的 blob
完全照所写的跑到了结束。它只是外泄了一个从未持有过你钥匙的盒子,
身处一道由硬件强制的边界的错误一侧,跟一切要紧的东西隔着这道边界。
传播步骤是一次写操作,而写操作要经过一个提示。
窃取钥匙只是 Miasma 的一半。另一半是扩散:它用窃取的 GitHub 访问
权限把自己提交进下一个仓库,正是这一点把一小撮被投毒的仓库变成了
73 个。即便是在一个正当地拥有 push 权限的配置文件里——比如你克隆
functions-action 恰恰是因为你打算对它提一个 PR——蠕虫的传播步骤
仍然得经由代理走出去,而那正是 Guardrails 迎面拦下它的地方。
Guardrails 读的是操作,不只是连接——它能分辨读和写。一个
git fetch 是读;一个 git push 是写。把一个配置文件的 GitHub
凭据设为写时询问,那么当代理伸手去做一个改变状态的调用的那一刻
——蠕虫提交它配置所需的 git-receive-pack、对某个 API 的一次
DELETE、对 EC2 的一次 Terminate*——Bromure 就在线路上把它拦下,
并在你的 Mac 上弹出一个提示,点名那个动词、那个目标和那个配置文件。
你给出的授权是有时限的:发布一次给十五分钟,吓人的那些只给一次性,
要是这个请求毫无道理就永不给予。读操作从不打断你;代理整天 fetch、
grep、读取都行。会暂停的是那个改动。
这就是「代理有一个令牌」和「代理可以拿这个令牌为所欲为」之间的差别。 Miasma 整套扩散机制就是一次代理从没告诉你它在做的写操作——而一次 代理从没告诉你它在做的写操作,恰恰就是读写提示被造出来要抓住的东西。 那次传播蠕虫的 push 变成了一个你点不允许的对话框,正如「代理删掉了 生产数据库」不再是一份事后剖析 而变成了一个被你回绝的提示。
那个版本只有几小时大,而 Bromure 让包变老。
Miasma——以及更广的 Mini Shai-Hulud 谱系——还有第二条触达开发者的
路径:不是通过一个你打开的仓库,而是通过代理在干活时安装的一个
刚被投毒的包。这场活动的 Red Hat 那一半正是如此,一个挂在某个
受信任 scope 下 32 个包上的 preinstall 钩子。而那些事件里残酷的
细节是时机:一个被攻陷的版本通常会在几小时内被发现并撤下——但那
正是一个自主运行、无人值守的代理可能把它拉下来的几小时。
Bromure 的供应链层把那同一个边界代理变成一个扫描检查点,而它 做了两件真正能对抗当日攻陷的事:
- 它强制把每一次拉取都对照 socket.dev 以及 OSV 扫描。 OSV 抓住 高于你所设严重度阈值的已知 CVE。socket.dev 抓住漏洞数据库还没 跟上的东西——流氓安装脚本、行为型恶意软件、typosquat,以及刚刚 发布的攻陷。一个被标记的版本会在 tarball 落进 VM 之前就被拦下。 关键在于这个扫描运行在代理之下、在边界代理处:无论代理怎样 重写自己的配置来绕开你,拉取仍然要经由那道它跨不过去的边界离开。
- 它强制执行一段冷却期。 Bromure 把过去两天内发布的任何版本
——可调——都隔离起来,于是一个一小时前上传的版本,在那个配置文件
里就根本装不上,直到生态系统跟上为止。面对一个全部机会窗口
就是发布和撤下之间那段空隙的蠕虫,冷却期并不是一个关于某个
包看起来坏不坏的启发式判断。它是一种拒绝——拒绝当那第一个去探明
真相的人。把它跟 Bromure 即时做的安装脚本剥除结合起来——把
postinstall钩子从 tarball 里拽出来并修正元数据哈希,让安装 仍能通过校验——那个真的落地的包,落地时就是惰性的。
对 Miasma 而言,仓库打开这条路径是头条。但同一场活动也会通过包来
扩散,而冷却期正是那个本可以让它的 npm 那一半挨饿的控制:一个新鲜的
@redhat-cloud-services 版本,或一个在调试那个 Microsoft 仓库时被
拉下来的、刚被投毒的传递依赖,会在它危险的那几个钟头里一直待在隔离区。
这在哪些地方救不了你。
一个你批准的 push 就是一个真的发生的 push。
读写护栏抓住的是代理没告诉你的那个写操作。它不读 diff。如果你
正当地在向 functions-action push,而你批准了那个提示,Bromure
就会转发这次 push——原则上也包括一个你在 diff 里没注意到的被
投毒的工作流。读你所批准的东西。
会话追踪会告诉你该读
哪些 diff。
冷却期是一扇窗,不是一堵墙。
两天是按观察到的发布-撤下间隙调出来的,但一个有耐心的攻击者可以 在一个被攻陷的版本上坐得比冷却期更久,到第三天仍然可被安装。 冷却期让当日蠕虫挨饿;它并不为一个仅仅是变老了的包做担保。 socket.dev 和 OSV 仍然得各尽其职。
配置文件是长期存活的,所以持久化会持续。
一个 Bromure 配置文件不是一块一次性磁盘。一个把自己写进配置文件 内部某个启动路径的载荷,可以存活到这个配置文件里的下一个会话。 它醒来时面对的,是一个没有主机钥匙的访客,和一个只会说短寿命的、 需经提示的、scope 受限令牌的代理——身处一个里头空无一物的盒子里 的存在——但终究是一种存在。
有意地把代理的 scope 限定好。
如果一个配置文件今天被配置为可以 push 到某个仓库,而这个配置文件 今天跑了 Miasma,一次被批准的写就会通过。中介授权之所以有效,是 因为它们窄。一个只需要读某个仓库的配置文件,就不应该能写它;一个 从不发布的配置文件,就不应该持有任何发布令牌。隔离遏制爆炸;scope 限定决定它本来最大能有多大。
下一个受信任的仓库已经被克隆到了某处。
TanStack 蠕虫的教训是 lockfile
和签名都不是防御。Red Hat scope
的教训是发布者也不是。Microsoft 加上了下一条推论:仓库也不是,
而且触发点甚至不必是一个你选择的安装——它可以是一个你的代理打开的
文件夹。Azure/functions-action 这个仓库因为受信任而并没有做错什么。
受信任正是一个规范的第一方 Action 的全部要点所在,而这恰恰是它值得
被投毒的原因——就 durabletask 而言,还被投毒了两次。
你没法靠更谨慎地去信任来修好这件事,因为这份信任从来没有错付。你 修好它的办法,是把事情安排成让「这是哪个仓库」和「这是哪个 scope 发布的」不再是你钥匙串所依赖的那些问题。Bromure Agentic Coding 就是这样一种配置:代理在一个按配置文件隔离 的 VM 里打开仓库,真实凭据待在主机上一个代理后面,代理做出的每一次 写都得过一个提示,而一个包在熬过冷却期之前根本装不上。一次被投毒的 文件夹打开,最坏能做的,就是外泄一个从未持有过你钥匙的盒子。它免费、 开源,今天就发布。