他们换掉的是地址,不是包
2026 年 8 月 31 日的十四个小时里,一部分发往 registry.coder.com 的请求,是由攻击者加进 Coder 自家 Cloudflare 源站池的服务器应答的。主机名没错,证书通过校验,模块带着一直以来的名字和版本,而回传的代码开始四处翻找 SSH 密钥、OIDC 令牌和数据库密码。Coder 手里没有攻击者的日志,所以它的公告要你自己去检查出站流量。这个问题,大多数跑着编码智能体的机器都答不上来。
主机名是 Coder 的。证书通过校验。模块带着你的模板早就钉死的名字和版本。8 月 31 日的十四个小时里,其中一些应答的服务器属于别人。
一位平台工程师在周一早上更新工作区模板。terraform init 从
registry.coder.com 拉取模板指名的模块(Docker、Zed、aider),跟上个月同一个
地方。Terraform 打印出它惯常的一片绿色,构建成功。那天早上、那整个月,没有任何
工具报警。
9 月 1 日,Coder 发布了 GHSA-vx42-ghc9-gw65, 评为严重,CVSS 9.0。用它自己的话说:“一名身份不明的恶意行为者取得了 Coder 的 Cloudflare 基础设施访问权,并向 Coder 模块注册表所用的池中加入了未经授权的 IP 地址。”在 8 月 31 日(周一)07:35 至 21:45 UTC 之间,一部分对 Coder 模块的请求 是由攻击者控制的机器应答的。Coder 做的是自托管的云端开发环境; BleepingComputer 列出 Dropbox、Palantir、Square、Mercedes-Benz、KKR、EnBW、美国政府机构和国防承包商 都在它的用户之列。
没有人动过那个包
一支谨慎团队所有的供应链习惯,检查的都是产物。名字拼对了吗。发布者是不是他 自称的那个人。这个版本是昨天才发的吗。锁文件的哈希还对得上吗。升版之前先读 diff。
攻击者改的是:你的客户端去问地址时,由哪些机器来回答;产物本身他没碰。他没有
发布任何包,没有接管任何维护者账号,没有注册任何近似名称,也没有编辑 Coder
注册表里的任何东西。名字一如既往地解析成功。TLS 握手是对着一张为真实主机名签发
的有效证书完成的。模块就叫 docker、zed、aider,因为那正是 Coder 模板会要
的模块,而且它带着模板早就钉死的版本号。
不一样的是回传的内容。被投毒的副本夹带了一个叫 dlp.sh 的 shell 脚本,为
docker、aider、rstudio-server、windows-rdp 和 zed 各模块各自构建了一份,公告里
也各自附了哈希。公告把这个载荷描述为“设计用来识别若干凭据,并把它们外传到一台
远程服务器,该服务器的域名与 Coder 经常使用的某个域名极为相似”的代码。那个域名
是 coder-infra[.]com,8 月 28 日注册,比任何人用到它早了三天,解析到
199.91.220.205,回调位于 http://www[.]coder-infra[.]com/cli/check。
它下手的清单,读起来像一份开发平台信任的盘点:配置器的环境变量与密钥、用户的 OIDC 令牌、SSH 密钥、外部认证凭据,以及 coderd 的配置变量,含数据库密码和认证 提供方凭据。
你不需要做任何反常的事就会落在那个时间窗里。按公告所说:“使用 Coder 主模块 注册表(registry.coder.com)作为模板来源,且在这段代码可获取的时间段内更新过 组件的用户,可能已经安装了恶意包。”创建模板、升一版模板、跑一次 dry run,或在 关掉模块缓存的情况下构建工作区,都是再普通不过的周一工作。
“检查防火墙、代理、DNS 和 VPC 流日志”
这是 Coder 补救步骤的第五条,也是整份公告里最有启发的一行。第一到第四条是 Coder 帮得上忙的事:找出时间窗内被缓存模块的 SQL、清掉它们的 SQL、打过补丁的版本 (2.37.0、2.36.4、2.35.7 和 2.34.9,全都在 9 月 1 日放出),以及一份该轮换的凭据 清单。第五条伸到这一切之外,因为能说出谁中招的那些服务器属于攻击者。Coder 看不 到它们,而且直说了:它无法确凿地认定每一个被入侵的部署。
于是问题回到你身上,而这是一个关于证据的问题。跑过那个模块的机器上,有任何东西
连过 coder-infra[.]com 吗?对一个生产环境的 VPC,多数团队一小时内就能回答,
因为平台团队里已经有人拥有流日志。
现在,换成你的编码智能体干活的那台机器再问一遍。智能体会跑 terraform init、
npm install、make、一次容器构建、一轮测试、一个它在仓库里找到的脚本、一个
某模块下载回来的二进制文件。这每一件事都会打开连接,而没有人把它们写在你一周后
还能检索到的地方。智能体自己的会话记录留着模型发出的工具调用,却没有任何一行
记录 Terraform 模块里的 shell 脚本对网络做了什么,因为模型压根没看见那件事发生。
第五步的答案没法事后再买。要么在请求离开机器的那一刻就有东西把它记了下来,要么 根本没有东西可查。
那个脚本本来会找到什么
就照载荷的说法,让它跑。它扫过环境找凭据,读走 SSH 密钥,抓走 OIDC 和外部认证
令牌,捡起里面带着数据库密码的配置变量,然后把整包发往 coder-infra[.]com。
Bromure Agentic Coding 给每个工作区一台跑在 Apple hypervisor 上的专属 Ubuntu 虚拟机,并以一个宿主机端代理作为客户机通往网络的唯一出口。在那里 跑同一轮扫描,它会带回完整一套凭据,而且全是假的。
ANTHROPIC_API_KEY 里放的是 sk-ant-api03-brm-… 诱饵,GH_TOKEN 是长度正确的
ghp_ 字符串,~/.git-credentials、~/.docker/config.json 和 ~/.kube/config
里放的是相应的占位值。格式都保留着,所以 gh、docker 和 kubectl 都会照收
不误。代理把你真正的值加密存在你的 Mac 上,等请求离开虚拟机之后才在线路上换进
去,而且只在请求要去的正是这份凭据所签发的那台主机时才换。没有开关能关掉这件事;
在这个 app 里,凭据本来就是这么工作的。
SSH 密钥是最干净的例子:虚拟机里根本没有密钥可读。SSH_AUTH_SOCK 指向一座跨越
虚拟 socket、通往宿主机上代理程序的桥,而那个协议里有一种请求叫“帮我签这个
挑战”,没有任何一种请求叫“把密钥交给我”。ls ~/.ssh/id_* 回来是空的,而
git push 照常工作。
AWS 值得单独一段,因为 Terraform 面对的正是它。虚拟机的 ~/.aws/config 指向一个
辅助程序,它吐出的是你真实的 access key ID,配上一把假的 secret key。
Terraform、AWS 的各种 SDK、boto3 和 aws CLI 都不用额外配置就会读这个辅助程序,
接着用假密钥签请求,产出一个格式完整、但 AWS 会拒绝的签名。宿主机在出去的路上
剥掉那个签名,用真正的密钥重新签一次。一个在环境里翻找 AWS 素材的脚本,带走的是
一个 key ID 和四十个什么都认证不了的字符。
接着它得把战利品发到某个地方,而工作区的出站防火墙就坐在那里。把 Unmatched
traffic 设成 Deny,虚拟机就只能连到你列出的主机,别的都不行。执行发生两次,在
客户机够不到的两个位置:虚拟网络交换机按目的 IP 和从 DNS 里嗅到的主机名比对每一
条流量,跨所有协议;代理再按 TLS 服务器名比对一次。没有人会把一个三天大的域名
放进那份清单。规则修改不用重启就能送达运行中的会话,所以你可以在构建还在跑的
时候补上 deny any coder-infra[.]com。
如果流量还是出去了,可能是工作区跑在默认的全部放行姿态下,也可能是目的地看着
太普通,那么诱饵就会做它的第二份工作。代理会扫描每一个出站请求的头部和正文,
寻找一份正发往“当初并非为它签发之处”的假凭据。一个寄往 coder-infra[.]com 的
sk-ant-api03-brm-… 字符串毫不含糊。代理会在任何一个字节抵达目的地之前用
HTTP 451 挡下,暂停虚拟机,并发出一条点名该凭据和两端主机的告警。Bromure 把这个
工作区标记为已入侵,在你抹掉它的磁盘和家目录之前,它不会再启动。
第五步的答案在事情发生的当下就到手,而不是一周后从日志里拼回来。
一条命令回答那份公告
宿主机代理会为每一个离开虚拟机的请求写下一条元数据记录,不论是哪个进程发出
的:时间、主机、端口、方法、路径、状态、延迟,以及在任何凭据替换之前量到的
字节数。把工作区的 Session trace 设成 Activity only,你会拿到这条
记录,而且完全不存储任何请求正文。接着
bromure-cli trace hostnames my-workspace 就会把这个工作区接触过的每一个不同
主机连同次数一起打印出来。Coder 的第五步,一行搞定,针对那台真正跑过代码的
机器。
各个引擎做了什么判断
Security Timeline(Window → Security Timeline…)是另一个视图,看的是 Bromure 的引擎判断了什么:每一次出站防火墙的裁决、每一份代理居中处理的凭据、 每一次供应链拦截,全部标上颜色、可以过滤。一条通往三天前才注册域名的被拒连接, 会在构建还在跑的时候就以一行红色的 Firewall 出现在那里,而不是等别人的公告告诉 你该往哪看之后,再去 grep 一个月的流日志。
客户机能碰到你 Mac 上的只有一样东西:共享文件夹,一扇通往你真实文件系统的实时
读写窗口,而入侵后的擦除按设计不会动它。共享项目目录而不是你的家目录,那么一个
猎捕 id_rsa 和 .env 的脚本,走遍的会是一个塞满检出和包缓存的 Linux 家目录。
当你无法检查来源,就盯住出口
所有来源验证最终都归结为同一个动作:把送到的东西,跟你已经信任的某样东西比对。 一个名字、一个发布者、一份签名、锁文件里的一个哈希。只要攻击者还必须往通道里放 点什么给你比对,这个动作就有效。握住通道一段的攻击者什么都不放进去,而 Coder 的 源站池就是通道的一段。8 月的 BdThemes 入侵是同一个形状, 一个被劫持的存储桶改掉了 35 万个 WordPress 站点会去拉取的 JSON 源,没有任何发行,也没有任何文件被 改动。
十四个小时就是一个工作日。Coder 抓到了,第二天早上就送出补丁,公布了哈希和指标, 也告诉读者该检查什么。但这份公告最后仍然要求每一位读者,自己去看自己的出站流量, 因为另一端的记录已经没了。
轮换公告列出的密钥、清掉缓存的模块、打上补丁。然后决定下一次要让它碰到什么, 因为下一次会从一条你从来没有理由怀疑的通道进来。 安装 Bromure Agentic Coding,给构建一台自己的机器,等公告出现 的时候,答案已经备好了。