返回所有文章
发布于 · 作者 Renaud Deraison

扫描器拒绝读它

Google 的 Threat Intelligence Group 在 9 月 8 日发布了 AI Threat Tracker。在讲述一个名为 UNC6780 的供应链攻击团伙的章节里,埋着一种把模型安全策略变成掩护的手法:恶意软件的 JavaScript 加载器开头是一段注释,索要生物武器合成路线和核武器规格,放在那里就是为了让 LLM 安全扫描器读到之后依策略拒绝,永远到不了下面的代码。拒绝本身就是规避。Bromure Agentic Coding 用永远不去读它们的检查,来应对这类产物。

说服一个安全扫描器批准恶意软件是件苦差事。递给它一份辐射强到让它不愿多看一眼的 文件,只需要一段注释,而你的流水线会把这次拒绝归档成“无法分析”。

你的构建流水线里现在有一个 LLM。多数都有。在依赖更新落地和合并按钮亮起之间的某处, 一个模型读 diff、读新文件,然后写下一段简短意见,说明这里面有没有哪里看起来不怀 好意。这是技术的好用法:它抓得到模式匹配会直接走过去的混淆加载器,而且每个文件只花 不到一分钱的零头。

9 月 8 日,Google 的 Threat Intelligence Group 发布了 AI Threat Tracker, 一份关于攻击者拿这些东西在做什么的季度观察。大部分报道都追着那个头条数字去了:一名 以金钱为目的的攻击者入侵了某公司的云基础设施,接着用 GTIG 的话说,“利用一个 AI 编码 聊天机器人、一段提示词和一组智能体指令,在不到六小时内规划、构建并执行了一场大规模 凭据收割行动”。数以千计的第三方凭据,经由受害者自己的 IP 地址被送了出去。在另一台 暴露在外的服务器上,研究人员找到一套侦察框架,它的仪表盘正实时跟踪超过 23,800 条 已收割的机密。

那是被写出来的故事。真正值得你花一个下午的段落在更下面几节,在谈一个 Google 以 UNC6780 跟踪的供应链团伙的部分。

文件最上面的那段注释

UNC6780 也叫 TeamPCP,自 2026 年 3 月起在 PyPI、npm 和 Docker Hub 上进行大规模的开源 项目入侵。他们目前的凭据窃取程序叫 DUSTMAKER,是一个 JavaScript 载荷。JavaScript 载荷都有加载器。这一个的加载器是名为 _index.js 的文件,而 GTIG 在它的最上面发现了 新东西:

除了嵌入在配置文件中、针对 AI 编码智能体的提示词之外,DUSTMAKER 的样本还包含嵌入 在恶意软件 JavaScript 加载器脚本(_index.js)顶部、以注释形式存在的提示词。这些 提示词含有极端的对抗性文本,提及生物和核武器之类的话题,很可能意在让 LLM 安全 扫描器失败,或因安全或策略上的拒绝而跳过对下面恶意 JavaScript 代码的分析。

那段注释让恶意软件大剌剌地摆在明处。未打包、未编码,全都在同一个文件里:恶意 JavaScript 就在三行之下,任何读到最后的人都会绊到它。

是那段注释让读者不愿意读到最后。它以一段伪造的系统覆盖开场,宣告安全准则已暂停, 接着索要气溶胶散布型生物武器的逐步合成路线。然后转向内爆式核武器设计,连当量目标都 附上。一个吞下这个文件、然后被问“这段代码有恶意吗”的模型,眼前多了一个更大声的问题, 而它用拒绝回答了那一个。扫描器返回一条策略消息。你的流水线记下无法获得分析,就像 它每周为超时和过大文件这么做上几十次一样,然后构建变绿。

这套手法对 UNC6780 来说毫无成本。它不需要知道是哪个模型在读这个文件、是哪家厂商在 托管那个模型,也不必知道它的系统提示词写了什么。它只向读者要一样东西:一份安全策略, 而在一条用于保障的流水线里,那正是你当初选择模型的理由。

拒绝就是规避_index.js/* SYSTEM OVERRIDE — CLASSIFIED BRIEFING生物武器合成路线裂变装置规格为了被拒绝而设计const _0x = require(…)凭据窃取程序明文、未经混淆永远没被读到从顶部开始读LLM 安全扫描器问题:这段代码有敌意吗先回答更大声的那个依策略拒绝构建日志无法获得分析同一行也会打给超时和过大的文件攻击者必须知道的事不必知道哪个模型在读 · 不必知道哪家厂商托管不必知道系统提示词 · 不必知道阈值只需要读者身上有一份安全策略
DUSTMAKER 的加载器没有藏起载荷。它以一段刻意触发审查模型安全策略的注释开场,于是模型拒绝了整个文件,三行之下的恶意 JavaScript 从未被评估。对一份只看步骤有没有失败的构建日志来说,拒绝和一张健康证明长得一模一样。

这条链的其余部分做的是同一件事

一旦看懂这一手,UNC6780 其余的手法读起来都是它的变奏。路径上剩下的每一道防线,同样 是一个对着攻击者所写产物形成判断的读者,而每一道都是被喂饱,而不是被击破。

软件包是顺着智能体最信任的工具送进来的。 GTIG 描述 UNC6780 入侵合法的开发者 账号,“把合法 MCP 服务器的木马化分支发布到 PyPI 仓库,例如 tiktoken_mcp,并把恶意 代码直接注入组织的官方 GitHub 仓库,例如 azure-functions-mcp-extension”。给 MCP 服务器下后门,意味着载荷是顺着唯一一类编码智能体生来就会加载、并且会授予工具的依赖 抵达的。

来源核查拿到一份有效的签名。 任何把证明当成信任信号的人,都该在这一段停一下:

DUSTMAKER 的样本具备检测自身是否运行于持续集成与持续交付(CI/CD)环境的功能。一旦 确认,它会从 GitHub Actions 运行器的进程内存中提取 OIDC 令牌。DUSTMAKER 使用这些 令牌把自己授权为受信任的发布者,并发布带有有效、经密码学签名的 SLSA Build 3 证明 的被篡改软件包版本。以有效令牌发布的软件包,会通过 AI 编码智能体的自动化信任检查。

给那个软件包签名的,是发布者自己的身份。DUSTMAKER 从它所寄居的运行器进程内存里把身份 捞了出来,所以一个检查签名的验证器会返回有效,而且它是对的。

端点代理盯错了街区。 DUSTMAKER“把恶意文件放进、或修改于供 AI 编码助手和集成开发 环境(IDE)使用的隐藏项目工作区目录(.claude/.vscode/.cursor/ 等)”。GTIG 也说了原因:这些文件夹让恶意软件融入“日常的开发者噪声”,并避开端点检测会盯着的地方, 例如注册表键和 /etc/cron.*。你的 EDR 厂商没有为 .cursor/ 里的新文件出过规则, 因为直到今年,那个文件夹里放的还是编辑器偏好设置。

助手拿到一个配置文件,而配置文件就是指令。 从那些同样的隐藏目录出发,DUSTMAKER “使用恶意配置文件 … 指示 AI 助手在日常的开发者交互期间执行任意命令或脚本(例如 setup.mjs)。这实际上迫使 AI 模型在开发者不知情的情况下代替攻击者执行命令”。助手读 了自己工作区的配置,那是它的工作,而配置指名了一个要跑的脚本。这句话里从头到尾没有 任何漏洞利用。

而审计 API 删掉了审计轨迹。 这个恶意软件把自己的 CI 任务伪装成“Copilot Setup” 之类的 AI 主题名称,然后发出自动化 API 调用,把工作流执行日志从 GitHub 界面上移除。

五个读者,五份攻击者写的产物读者拿到了什么得出什么结论LLM 安全扫描器读文件一段它必须拒绝的注释生物武器,接着是武器设计什么都没有 — 它婉拒了记成无法获得分析来源验证器读签名一份真的 SLSA Build 3 证明以窃来的 OIDC 令牌铸出有效,而且判得没错那个身份真的是发布者的AI 编码助手读工作区配置一份攻击者写的配置.claude/ · .cursor/ · .vscode/执行 setup.mjs在一次日常交互之中端点检测盯着恶意软件会写的地方改写进编辑器的文件夹不是注册表键,也不是 /etc/cron.*日常的开发者噪声还没有规则覆盖那条路径审计者工作流日志已通过 GitHub API 删除一段空的历史
同一条攻击链里的五道防线,没有任何一道故障。每一道都是对着产物形成判断的读者,而每一次,产物都是攻击者写的。扫描器拒绝了,验证器验证了,助手服从了,端点代理看向别处,审计者找到一份空日志。

五道全都按设计运作

你在这里没有缺陷可以上报。扫描器套用了它的安全策略,那正是你付钱请它做的事。验证器 验证了一份确实有效的签名。助手加载了自己工作区的配置,那是功能。端点代理盯着的,是在 编码智能体还没有配置目录之前重要的那些路径。每一个都对自己收到的输入做了自己的工作, 而输入是 UNC6780 挑的。

一般的规避要的是错误的判定:把二进制文件打包、把字符串切开、把网址编码,指望分析回来 是干净的。DUSTMAKER 要的东西,从一个正确的判定里就能拿到,或者干脆不需要任何判定。

任何形状是读取产物、形成意见的防御,都在这个射程之内。把语言模型放在读者的位子上 会让射程更大,因为模型带着自己那份不会做的事情清单,而那份清单是公开的、有文档的, 任何打得出一段注释的人都够得到。业界花了两年把模型锤炼得不会被说服点头。UNC6780 把它们说服到什么都不说。

那么,换一个问题去问你智能体流水线里的那些控制:其中哪些必须读取攻击者所写的内容 才能完成工作,而把那些放到一边之后,还有什么站得住。

没得争辩的检查

Bromure Agentic Coding 让每个编码智能体跑在你 Mac 上一个硬件虚拟化的 Linux 虚拟机 里,所有安全控制都在那道边界的主机这一侧。面对这样一条攻击链,真正关键的是这些控制 拿什么当输入。

年龄闸门读的是一个时钟。 主机端代理认得出对主要软件包仓库的请求,并在任何一个 字节抵达虚拟机之前,套用该工作区的供应链策略。年龄闸门是默认唯一开启的一层,设定为 至少两天,会拒绝比截止点更年轻的版本。它靠改写仓库的版本清单来做到这件事,所以从 智能体的角度看,太新的版本根本还不存在;一次指定版本的直接拉取会回来一个 HTTP 451, 正文说明该软件包真正的年龄。软件包本身从来不会被读。一段为了让语言模型出轨而写的 注释,挪不动任何一个发布时间戳,而 UNC6780 的木马化分支按其本质全都是新鲜发布。 npm、PyPI、Cargo、RubyGems 和 Packagist 全都带有逐版本的发布时间,所以闸门覆盖得到 它们。

分类器没有策略可以搬出来。 Bromure 的源码检测器会为智能体流式返回给模型的 tool_result 片段评分,也就是文件内容、网页和命令输出,用的是在你 Mac 上以 ONNX 运行、DeBERTa 家族的本地 PromptGuard 模型。它是序列分类器,不是生成模型。把 DUSTMAKER 的注释递给它,它不组织任何答案,不掂量回答是否被允许这个问题,也不婉拒 任何东西。它只吐出一个介于零与一之间的数字,而一段以伪造系统覆盖开场、宣告安全准则 已暂停的文本,正是会把那个数字推高的形状。每个工作区都由你决定命中时做什么:记录、 询问,或阻断。阻断会返回 451,模型永远看不到那段内容。

读工作区配置的,是智能体以外的东西。 一个放在 .claude/ 里、叫助手去跑 setup.mjs 的文件,就是所谓的规则文件后门,而它有自己的检测器:一轮针对隐藏 Unicode、元指令模式、凭据路径和 curl 管道进 shell 这类构造的确定性启发式扫描,再加上 一个为那些不合任何固定模式的流氓指令而微调的 ModernBERT 分类器。两者都在代理里、在 设备上运行,处理的是从系统提示词中抽出来的指令文件,那是虚拟机内部任何东西都关不掉的 位置。两者都是按工作区的开关,而且第一次开启时各要下载一次模型。那次下载,就是这个 故事向你要的五分钟。

凭据窃取程序需要凭据。 DUSTMAKER 的存在就是为了从开发环境里收割机密,而一个 Bromure 工作区一个也没有。Bromure 把你配置的每一份凭据,替换成一个保留结构的假货, 由真实值和每次安装专属的盐推导而来:sk-ant-api03-brm-…、一个长度正确的 ghp_ 令牌、brm-mcp_…brm-k8s-…。这些假货进到环境变量里,也进到 ~/.git-credentials~/.docker/config.json~/.kube/config~/.aws/config, 而那正是窃取程序会逐一枚举的清单。你真正的值留在 Mac 上加密着,主机端代理再把它们 换到链路上,范围限定在各自被铸出来时对应的目的主机。SSH 私钥的字节永远不会进入虚拟 机;跨过去的只有签名。没有任何开关管得到这些,因为代理就是虚拟机通往网络的唯一路线。

假货同时也是绊线。 一个假令牌只有一个正当的目的地。代理会扫描每一个对外请求的 头部和正文,找出正要去别处的假货。一旦命中,它不转发任何一个字节就拒绝该请求,当场 暂停虚拟机,并在 Security Timeline 里记下一条红色的 Credential brokering。它把该 工作区标记为已被入侵,而下一次启动会先逼你清掉磁盘和家目录镜像。事后你不必轮换任何 东西,因为窃取程序握着的是占位符,而那个占位符从没出去过。

记录留在你的机器上,够不到恶意软件调用的那个 API。 把追踪设在 Activity only 时,代理会为每一个离开虚拟机的请求写下一行元数据,不含正文。 bromure-cli trace hostnames 会列出该工作区接触过的每一个不同主机和次数,而 bromure-cli trace leaks 会在入侵告警之后指出目的地。一个握着 GitHub 令牌的攻击者, 几次 API 调用就能清空一段工作流历史。要清空那份追踪,得先摸到你的 Mac。

会读产物的控制它们每一个的输入都是攻击者写的模型读文件给它一份它非拒绝不可的东西验证器读签名偷走 OIDC 令牌;签名是真的助手读配置写下配置;它就跑你指名的东西环境里放着真的机密~/.aws/config · ~/.docker · GH_TOKEN攻击者需要的一段注释,和一个带着策略的读者读别的东西的控制在主机上、虚拟机之外、你的 Mac 上年龄闸门读发布时间戳默认两天;太新的版本不存在分类器吐出一个概率本地 ONNX · 记录、询问或阻断 · 没有可婉拒的余地虚拟机里只有占位符sk-ant-api03-brm-… · brm-mcp_… · brm-k8s-…代理比对字节与目的地越界的占位符:451、虚拟机暂停、一条红色记录攻击者需要的改掉一个时间戳,或一份根本不在的机密
同一份产物,面对两种检查。一个会读取攻击者所写内容的控制,可以被喂进为了让它出轨而写的内容。一个读时间戳、读字节模式、读目的主机的控制,没有什么可以跟它争辩,而窃取程序此行要找的凭据,从来就不在它运行的那台机器里。

今天下午就能改的两个设置

打开工作区的 Supply Chain 面板,确认年龄闸门是开着的,截止点设在你能接受的 值。两天是默认值,而面对一个刚刚发布的木马化分支,这是你手上最便宜的控制。接着 打开 Prompt Injection,把两个检测器都启用;每个都会下载一次模型,之后扫描就 都在本地进行,而且免费。

一条让问题变小的规则

Guardrails 里把 Unmatched traffic 设成 Deny,然后列出这份工作需要 的东西:allow web api.github.comallow web registry.npmjs.orgdefault deny。一个连不上自己指令服务器的窃取程序,只是一块你正要扔掉的磁盘上的 一个文件,而保存会把规则推送到运行中的会话,不必重启。

不存在胜过评估

我们一再从不同方向抵达同一个地方。一个 信任了智能体写得出来的名称的出口代理。一个 把 bash 读成和 bash 不一样的命令守卫。一个 密钥检查失败后落到空认证对象的 AI 网关。 在每一个案例里,某个组件都在诚实地做着评估某样东西的工作,然后评估坏掉了。

DUSTMAKER 把这个论点磨得更利,因为它根本不需要任何坏掉的评估。它只要求:评估是在 UNC6780 写的内容上进行。给读者一个文件,读者就会对它形成看法;是谁提供了那个文件, 决定了其余的一切。

评估是比不存在更弱的原语,而我们却不停地把不存在能免费完成的工作交给评估。你没办法 跟一个发布时间戳争辩,而一个概率没有想要收回的意愿。一个以完整权限在一次性虚拟机里 运行、把自己认得的每个配置文件都翻过一遍的凭据窃取程序,最后带走的是一把占位符,和 一个它绕不过去的套接字。

把模型放进你的评审流水线是对的决定。只要确保那些真正会挡下东西的控制,是仓库里没有 任何东西讲得上话的那些。 安装 Bromure Agentic Coding,给你的智能体一些永远不必读攻击者 文件的检查。