这类 Bug 可能存在于如今发布的每一个多智能体(multi-agent)AI 产品中。而修复它的设计模式,在这个领域里还无人提及。
这是完整的故事。
我并不是想黑进任何系统。我只是在为我自己的智能体基础设施(agent infra)工作,研究 Perplexity Computer 是如何处理沙箱(sandbox)的。我想弄明白生产环境中的多智能体系统究竟是如何隔离执行环境的,什么被共享了,什么没有。
首先,我开始在系统里四处探索。我注意到沙箱中安装了 Claude Code。
我让智能体启动它,并生成了一些测试代码,纯粹为了看看它的表现。运行得很顺利。标准的 Claude Code,运行在 bypass-permissions 模式下,所以它不会提示确认。这对于智能体系统来说很合理。
就在那时我想到:等等,他们是如何处理 API 密钥的?!Claude Code 需要一个 Anthropic API 密钥才能工作。那个密钥肯定就存在这个沙箱的某个地方。它是怎么被注入的?它的作用域是什么?它是被隔离的吗?
正是这个问题引导我走上了这条路。
子智能体(subagent)在其进程环境中持有一个 API 密钥。我需要把它偷出来。
Perplexity Computer 尝试了六次,都失败了。
- 要求子智能体 dump 其环境变量:被拒绝!
- 在共享文件系统上放置了一个木马脚本:它读取了我的代码,理解了它在做什么,然后拒绝运行它!
- 在
.bashrc和.profile中下毒:它们触发得太早了,在 API 密钥被注入之前。 - 在 PATH 上丢了一个假的 node 二进制文件:从未被触发。
- 衍生出编码智能体并同时监视进程树:子智能体运行在不同的沙箱中,什么也没捕获到 :(
- 寻找 Claude Code 的系统提示词并修改它以移除拒绝行为:找不到正在被注入的自定义系统提示词。
六次失败。Claude 在模型/提示词级别的安全性实打实地出色。每次我尝试耍点聪明,模型都能察觉到。

然后我想起 Claude Code 是一个通过 npm 启动的 Node.js 应用。而 npm 会从主目录读取 ~/.npmrc。且主目录位于我可以写入的共享文件系统上。
Node.js 有一个 --require 标志,可以在任何应用代码运行之前预加载一个 JavaScript 模块。你可以通过 .npmrc 中的 NODE_OPTIONS 来设置它。这意味着如果我往共享主目录写一个 .npmrc,子智能体衍生的每个 Node 进程都会预加载我的代码!这发生在 Claude Code 初始化之前,在任何安全检查运行之前,在任何事情发生之前!
漏洞利用只需三条命令:
- 编写一个六行脚本,将
process.envdump 到共享工作区的一个文件里 echo 'node-options=--require /path/to/script.js' > ~/.npmrc- 要求 Perplexity Computer 做随便什么编码任务
子智能体启动 → npm 读取我的 .npmrc → 我的预加载在 Claude Code 开始前触发 → 完整的 API 凭证落入了共享文件系统。
成功了!它给了我一个 Perplexity 网关令牌(gateway token),该令牌通过 agent-proxy.perplexity.ai 代理到他们主 Anthropic 账户。

自然,我做的第一件事就是在我的笔记本电脑上为 Claude Code 设置这个 API 密钥和 BASE_URL。我原以为来自 Claude Code 的 LLM 调用会失败并被限制在沙箱内。结果让我震惊。Opus 4.6 瞬间给出了响应!
我接着想,“他们肯定会把这笔使用费记在我的账户上,这个 API 密钥一定绑定了我的用户”。我又错了。
我让 Opus 4.6 生成了一个长篇故事,描述包括每一个发明、帝国和发现在内的世界历史。我并行运行了这个调用 5 次,每次生成超过 10 万个输出 token。这本该消耗掉我所有的 Perplexity Computer 积分,但它们纹丝未动。
没有 IP 限制。没有会话级作用域(session-scoped)。没有沙箱绑定。账单算他们的。
这个星球上资金最充足的 AI 初创公司之一,竟然被一个自 2019 年以来就在 Node.js 供应链攻击中使用的点文件(dotfile)给拿下了。
模型做对了所有事。基础设施却没有。
现在,这是我真正希望构建智能体基础设施的创始人从中吸取的教训。
Perplexity 的架构对了一半。他们在沙箱和 Anthropic 的 API 之间使用了一个代理。这是正确的模式。你永远不应该把原始提供商 API 密钥放进沙箱里。代理赋予了你控制权、可观测性,以及在不轮换主密钥的情况下撤销访问权限的能力。
问题在于他们的代理令牌与执行上下文零绑定。一旦你拿到它,它就可以在任何地方永远生效。
以下是正确的做法:
将令牌绑定到沙箱 ID。 令牌和沙箱 ID 不匹配?拒绝。密钥泄露但你没有沙箱?没用。理想情况下,它还应该将令牌绑定到沙箱 IP 地址,但 E2B(他们使用的沙箱提供商)在沙箱初始化之前并不提供该信息。
让令牌成为临时(ephemeral)的。 在沙箱启动时铸造它。在沙箱暂停时销毁它。不要有长生命周期的凭证。代理在会话开始时生成一个短生命周期的令牌,并在拆除时使其失效。从已死沙箱泄露出的密钥,就是一把死密钥。
将令牌绑定到用户的计费账户。 即使其他一切都失败了,即使有人从活跃沙箱中窃取了一个实时令牌并在其过期前使用,使用费也会算到衍生该会话的账户头上。而不是一个共享的主计费池。这就把“无限制的免费 API 访问”变成了“某人在滥用他自己的配额”,两者的严重程度完全不同。
这三件事——沙箱绑定、临时性、用户计费——才是让代理模式真正起作用的关键。没有它们,你只是在增加一个什么都拦不住的额外网络跳数。
这不是 Perplexity 独有的问题。这就是目前智能体基础设施的默认架构,因为它是构建起来最快的。智能体之间共享文件系统、长生命周期凭证、主账户计费。我敢打赌,如今生产环境中的大多数多智能体产品都有这种模式的某种变体。
在发布前已报告给 @AravSrinivas 和 @denisyarats。