你的智能体幻觉出的软件包现在已存在 —— 而且它是恶意的
每个安全团队对拼写抢注 (typosquatting) 都有一个心智模型:攻击者注册 requets 并等待有人误输入 requests。这行得通,但它是对人类粗心大意的一种随机押注。Slopsquatting 则更糟糕,因为这种“拼写错误”并非随机。语言模型会以可预测、可重复的模式虚构出看似合理但并不存在的包名——攻击者可以查询你使用的相同模型,收集它们虚构的名字,并准确地在 PyPI 和 npm 上注册这些包。幻觉变成了预购单。你的编码智能体 (coding agent) 在拥有自主安装权限的情况下运行,就是那个取走包裹的顾客。
这并非假设。关于这一现象的最大规模研究对 16 个模型生成了 223 万个代码样本,发现 19.7% 的推荐包并不存在——涉及 205,474 个独特的虚构名称。当一位安全研究员将其中一个最常被幻觉出的 Python 包注册为一个无害的空壳时,它在三个月内被下载了超过 30,000 次,并最终出现在一家大型科技公司开源仓库的安装说明中。由“氛围编程” (vibe coding) 带来的供应链攻击已经完成了它的概念验证 。
幻觉具有集群性,这使其成为可攻击的目标
大多数人之所以不担心这个问题,是直觉在作祟:他们认为幻觉是随机噪声,因此攻击者无法预测。这种直觉是错误的,数据也给出了明确的答案。
在德克萨斯大学圣安东尼奥分校研究人员的一项 USENIX Security 研究中,当触发幻觉的提示词被重复运行 10 次时,43% 的虚构包名在 每一次 运行中都会出现。58% 的包名重复出现超过一次。这些不是宇宙射线造成的比特翻转;它们是模型概率分布中稳定的“吸引子”。一个相信 starlette-reverse-proxy 应该存在的模型,在不同的会话、不同的用户,甚至通常在不同的模型版本中都会坚持这一观点——因为这种信念与其真实知识来源相同:对训练数据进行的词素组合 (morpheme composition)。这个名字 听起来 很对。它遵循生态系统的命名规范。如果有人写了这样一个包,它确实 应该 叫这个名字。
这种稳定性彻底改变了经济账。攻击者不需要猜测你的开发人员会打错什么。他们针对常用提示词(如“设置 FastAPI 反向代理”、“解析该厂商的日志格式”)运行流行模型,收集重复出现的幻觉依赖项并进行注册。候选列表是可枚举的,收集是自动化的,而注册是免费的。商业模型产生幻觉的频率(约 5% 的推荐包)比开源模型(约 22%)低,但当输出如此紧密地集群化时,“低频率”并不能提供任何安全保障。
空包实验表明需求端也是真实的。注册了虚构的 huggingface-cli 包名的研究人员没有做任何推广。然而,数以万计的下载依然纷至沓来,由每一个不断产生相同名字幻觉的模型实例引导而来——甚至进入了阿里巴巴 GraphTranslator 项目的 README 中,人类用户在那里直接复制粘贴安装命令,而没有检查该包是否是他们所认为的那个。现在想象一下,如果那个包不是空的呢。
智能体将糟糕的建议转化为已执行的入侵
在 Copilot 时代,包名幻觉只是一个带有内置“断路器”的烦恼:人类运行安装,看到 ERR! 404 Not Found,咕哝几句,然后去找真正的包。幻觉在任何代码运行之前,就以巨大的声响宣告失败了。
智能体从两个方向移除了这个断路器。首先,拥有 Shell 访问权限的智能体不只是 建议 执行 pip install fastapi-jwt-middleware——它直接在旨在优化构建通过的循环中运行它,而没有人类面对陌生名字时产生的那一丝迟疑。其次,不太明显的一点是,智能体消除了“失败的声响”。如果包不存在,智能体会读取 404 错误,进行调整并尝试其他方法;没有人会知道模型曾经想要一个虚构的包。但如果攻击者已经注册了这个虚构包,安装就会 成功——智能体得到了绿色的对勾,任务完成,而恶意依赖项则通过一个看起来完全正常的提交进入了代码库。
安装时的代码执行使这种威胁变得立竿见影。npm 生命周期脚本和 Python 构建钩子在包落地的瞬间就会运行任意代码,这发生在智能体“使用”它之前,发生在任何测试执行之前,也发生在任何评审之前。而且,智能体的执行环境是一个极具价值的目标:它通常持有环境凭据——云密钥、注册表令牌、开发者的 SSH 代理——因为我们为了让智能体完成工作而赋予了它我们的权限。在并非真正沙箱的智能体沙箱中,一次被劫持的安装是一个凭据窃取事件,而不仅仅是一个代码质量事件。
最近对智能体编码工具的评估显示,前沿技术在改进,但风险并未消失。与原始模型输出相比,能够验证其工作(运行安装、读取错误、在任务中间查阅文档)的高级 CLI 智能体将虚构依赖项减少了约一半,而能够实时对比在线注册表索引验证名称的配置表现更好。但在测试的每种配置中,仍然会产生边缘情况的失败,通常出现在请求多个依赖项的复杂提示词中。将每个任务都会重新产生的攻击面减半只是缓解,而非消除。模型组合出看似合理的名字的倾向并不是一个可以被修复的 Bug;它是让模型能够编写代码的相同机制。
为什么“审查 Diff”无法拦截它
通常针对智能体(agent)风险的对策是人工审查智能体的输出。对于“垃圾抢注”(slopsquatting),这种审查在结构上是失效的,并不是因为审查者偷懒。
想象一下审查者实际看到的:一个实现了某项功能、通过了 CI、并且在 package.json 中包含一行新代码的 400 行拉取请求(PR):
- https://arxiv.org/abs/2406.10279
- https://www.usenix.org/system/files/conference/usenixsecurity25/sec25cycle1-prepub-742-spracklen.pdf
- https://www.theregister.com/2024/03/28/ai_bots_hallucinate_software_packages/
- https://www.trendmicro.com/vinfo/gb/security/news/cybercrime-and-digital-threats/slopsquatting-when-ai-agents-hallucinate-malicious-packages
- https://nesbitt.io/2026/04/09/package-security-defenses-for-ai-agents.html
- https://www.infosecurity-magazine.com/news/ai-hallucinations-slopsquatting/
- https://daniakash.com/posts/simplest-supply-chain-defense/
