那条命令的开头是 npm install
2026 年 8 月 24 日,Cato Networks 的 CTRL 团队公开了一场攻击行动:它买下“codex macos download”的 Google 赞助搜索结果,把开发者带到一个伪装成 OpenAI Codex 门户的 Google Sites 页面,再请他们把一条命令粘贴进终端,而那条命令的第一行,是真正的 Codex npm install。这一行之后的所有内容都是 base64。Bromure Agentic Coding 从不让任何人去寻找编码代理:安装命令是 Bromure 自家镜像目录里的一段固定字符串,它在一次性的 Linux 虚拟机中运行,虚拟机里的凭据都是诱饵,而只要把未匹配的出站流量设为拒绝,加载器的那三次抓取就永远出不了这台机器。
两年来,业界一直在为编码代理“装了什么”构建控制。这场行动往前多走了一步, 转而污染你“怎么安装”编码代理。那一步没有注册表,没有锁文件,也没有来源 证明。它只有一个搜索框、一个看起来没问题的页面,以及你自己放在键盘上的手。
一位开发者想在自己的 Mac 上试试 OpenAI 的 Codex CLI,于是在 Google 里敲下
codex macos download。第一条结果是赞助的,排在 OpenAI 自己的条目之上。它通往
一个干净的下载门户,标识对、排版也对,托管在 sites.google.com 上。页面写着:
打开终端,把这个粘贴进去。
他粘贴了。命令以 npm install 开头,包名也是对的。
8 月 24 日,Cato Networks 的研究团队 CTRL 发布了 这场行动的分析, 同日被 Infosecurity Magazine、 SiliconANGLE 和 Techzine 转载。最终载荷是一个 macOS 窃密程序。真正值得写成报告的,是它的投递方式。
页面由 Google 托管,诱饵由别人托管。
Google Sites 是个建站工具。任何人只要有账号,就能在 Google 域名上拥有一个页面, 而证书、锁形图标和网址的前半段,全都属于 Google。那就是被租来的信任。
页面本身什么也没有。Cato 在上面完全没找到恶意代码。诱饵是通过一个指向攻击者
基础设施的 iframe 送达的;按 Cato 的分析,它还经由 Google 的静态内容代理转发,
所以连第二跳看起来都像是 Google 在抓取什么东西。Techzine 点名了其中两个 iframe
主机:bright-links[.]com 和 swiftsaverfin[.]com。
这种切割为运营者买到了两样东西。看得见的那个页面永远不变,所以它不会触发信誉 系统,也不会被重新审查;而他们可以随时在 iframe 的另一侧替换载荷。Cato 数出 三组彼此独立的基础设施,其中第三组复用了第一组的一个 iframe 主机。
其余部分读起来就像一场成熟的广告欺诈运营。活的诱饵放在一个混淆过的路径
/codexx/ 下面,而 /codex/ 提供的是无害的东西。运营者会对访客做指纹识别,任何
看起来不像合理 macOS 目标的访客,都只会看到良性内容——那是他们给研究人员和沙箱的
回答。域名会轮换。载荷网址也会变。
第一行是真的
ClickFix 不给你一个文件。它给你一条指示,让你自己把载荷搬过边界。用 Techzine 的话说:“没有任何文件被下载,自然也没有杀毒方案能拦。”
粘贴的那条命令,开头是一段看起来完全合法、属于 Codex 的 npm install 字符串。
整个把戏就在这里。一个正要安装 Node CLI 的开发者看到 npm install,看到自己搜索
的包名,大脑就把这一行归档成正确。后面那团 base64 只隔着一个 shell 运算符,就在
同一行上,在复制框折行的另一侧。当一条命令的前半段正是你预期的命令时,你不会去读
它的后半段。
解码之后,它会抓取一个远程脚本,再把它管道给 zsh。接下来是三个阶段。
第一阶段是一个用死代码和未使用变量注水的 shell 脚本,里面包着一团编码过的
数据,最后交给 eval。注水的存在,是为了打败特征匹配,也是为了让随手翻看文件的
人放弃。
第二阶段在做任何其他事之前,先带着 event=pasted 这个参数向攻击者回传信标。
那是一张投递回执。另一端有人正在测量他们各个落地页的“粘贴率”,就像营销人员测量
转化率一样。
第三阶段把二进制文件下载到 /tmp/helper,执行 xattr -c 去掉 macOS 加在所有
来自网络之物上的隔离属性——正是这一步让 Gatekeeper 的警告消失——然后标记为可执行
并启动它。这个二进制文件是通用 Mach-O,所以在 Intel 和 Apple Silicon 上都能跑。
Cato 指出,运营者仍在持续开发这个加载器。较早的样本使用带压缩的 base64;较新的 则使用 AES 加密的 gzip 容器,其密钥是从散落在脚本各处的变量重新拼装出来的。
至于最终载荷,Cato 把这套投递框架与 Atomic macOS Stealer 联系起来:加载器
网址的结构、遥测请求的形状、/tmp/helper 这个暂存路径、xattr 的移除、以更新
为主题的载荷网址。他们称这些重叠既强烈又一致,但止步于给那个二进制文件命名——这是
正确的判断,在转述时也值得一并保留。
扫描器上游的那一步
把业界花了两年、为编码代理打造的那些控制排开来看。注册表信誉扫描。锁文件与完整性 哈希。构建来源与证明。SBOM。新包版本的最短存在时间门槛。对生成代码做静态分析。 它们都有效,而且都从同一个假设出发:开发者已经有一套能用的工具链,风险在下游, 在他拿它来做什么。
这场行动坐在这一切的上游。这里的赝品是代理本身,而它在任何注册表之前、任何 锁文件之前就已抵达,以一条开发者用自己的手敲进自己终端的命令抵达。扫描器没有任何 制品可以打分,因为那个制品是网页上的一句话。
锁定目标是这门经济的另一半。会去竞价 codex macos download 的人,买的是一台属于
“此刻正在搭建编码代理”之人的机器:一台在一小时内就会装着 API 令牌、SSH 密钥、
一份 ~/.aws/credentials 和一枚 GitHub PAT 的机器,而这些东西窃密程序全都知道
去哪里找。安装的那一刻,是待在现场最便宜的时间点。
我们写过这件事的反面:在 FakeGit 与 AgentBaiting 里,是代理 去搜索,再把安装步骤递给人。这里则是人去搜索。两者都成立,因为“先发现、再粘贴” 就是软件被安装的方式,而这个回路的两端都没有扫描器坐镇。
安装命令从哪里来
Bromure Agentic Coding 没有这样的回路,原因就在代理是从哪里 来的。
你不会去寻找编码代理。Codex 是配置文件 Agents 面板里的一个开关,就在 Claude Code 和 Grok Build 旁边。把它打开,选好它怎么认证,下一次会话开始时代理就在那里 了,在配置文件的虚拟机内某个 kitty 标签页中运行。
那个开关背后是一段固定字符串。Bromure 从自家目录预置基础镜像,而 Codex 的条目只有 一行:
npx --yes @socketsecurity/cli npm install -g --silent @openai/codex
Claude Code 按同样的模式是 @anthropic-ai/claude-code,Kimi Code 是
@moonshot-ai/kimi-code。包名是编译进应用里的,不是被发现的。注册表是
registry.npmjs.org,不是某个 iframe 指定的任意主机。安装过程被包在 socket.dev
的 CLI 之外,抓取动作则经由宿主机的 MITM 代理送出,配置文件的 Supply Chain
策略就在那里生效:默认开启的两天存在时间门槛,加上 OSV 查询、socket.dev 或 Delpi
过滤,以及你可以打开的安装脚本剥除。
那条路径没有搜索结果,也没有可供冒充的页面。攻击者花钱去污染的那个渠道,根本不在 这套工作流里;而“不存在”不是一个你得记得去操作的控制。
那就照样粘贴看看
架构必须撑过有人无视它的那一天。把这场行动拿过来,让它赢,然后带着它走一遍配置 文件:有人找到那个假门户,复制了命令,把它粘贴进自己做代理工作的终端——在 Bromure Agentic Coding 里,那是虚拟机内的一个 kitty 标签页。
那个二进制文件跑不起来。 通用 Mach-O 是 macOS 的可执行文件。客户机是 Ubuntu。
xattr 不是 Linux 命令,而 /tmp/helper 是一个位于“本来就是要被丢掉”的系统盘上
的路径。Resources → Storage 提供给 /home/ubuntu 的 Erase home…,以及
Reset to base…——从共享的、只读的基础镜像重新克隆工作区的系统盘。
那三次抓取出不去。 这条链的每个阶段都需要网络:base64 解码后那个拉取第一阶段
的网址、event=pasted 信标,以及载荷下载。Guardrails → Outbound connections
是一张 pf 风格的规则表:动作、协议(tcp、udp、web、any)、主机或 CIDR、
端口,web 还可以列 HTTP 方法;自上而下匹配,第一个命中者胜出,而
Unmatched traffic 可设为 Allow 或 Deny。把它设成 Deny,再列出你工作真正需要的
主机就行。bright-links.com 不在其中。Bromure 在宿主机侧、在虚拟交换机和代理里
强制执行这张表,所以客户机对自己环境做的任何事,都改变不了答案。
值得坐下来细看的是第二阶段。event=pasted 是运营者的转化指标,是那个告诉他们某个
落地页有效、以及有多有效的 ping。当这个请求死在宿主机上,他们拿到的不是一份感染
失败报告。他们拿到的是沉默,而沉默读起来就像一次没有转化的广告展示。
窃取拿回去的是占位符。 Atomic 这一级的窃密程序,盯的是浏览器数据、加密钱包,
以及——在开发者机器上——凭据。在 Bromure 的配置文件里,真正的值从来不会进入虚拟机。
通用 API 密钥是以环境变量导出的 brm_… 假值,由宿主机代理在传输途中替换掉。
kubeconfig 是合成的,配一套用完即弃的客户端证书。AWS 请求会在宿主机侧重新签名,
所以任何绕过代理的东西都会拿到 InvalidSignatureException。
~/.docker/config.json 里放的是一团假的 base64。SSH 私钥根本不在里面等着被读:
它们从来就不在。逐条为凭据打开
Require approval to use,每一次替换就会变成一个宿主机侧的对话框,附带一段有
时限的授权:五分钟、一小时,或这次会话的其余时间。一个配置文件最多只共享你挑选的
八个文件夹,所以 Mac 的其余部分并不是“多走几层目录”就能到的地方。
关联分析早就在你屏幕上了。 Cato 自己的结论是,这场行动的任何单一阶段都不会 让它现形;要检测到它,得把赞助搜索的投递、内嵌的网页内容、终端里的执行,以及出站 活动放在一起关联。当你的证据分散在浏览器、shell 和网络抓包里,那是个难题。在 配置文件里,它只是一个窗口:Security Log(Window → Security Log…)是一张 宿主机侧的单一时序表格,记录包判定、防火墙决策、凭据替换与提示注入检测。客户机 代码无法编辑它,因为客户机代码根本够不着它。
把它打开
存在时间门槛默认就是开的,设在两天。其他层次都是设置,而且值得花两分钟: Guardrails → Outbound connections,把 Unmatched traffic 设成 Deny,再为你 工作需要的主机建一份允许清单。Supply Chain → OSV vulnerability check,如果你 有密钥,再加上 socket.dev 或 Delpi 过滤,还有安装脚本剥除。Credentials → 对任何能花钱或能删数据的东西打开 Require approval to use。然后把 Security Log 窗口开在第二块屏幕上放一个星期,看看你的构建到底都在跟谁说话。
赞助搜索结果还会继续当那道前门,受信任域名上的建站工具也还会继续出租门廊。Google 会下架这些页面,然后又会有三个新的立起来,因为这门生意的算式是“每次点击几美元” 对上“一台装着云凭据的开发工作站”。 安装 Bromure Agentic Coding,从配置文件编辑器而不是下载页启用 你的代理,让下一条被粘贴的命令,落在一台生来就是要被丢掉的机器里。