你的Agent中间商你从未审查过
研究人员从淘宝、闲鱼和基于Shopify的店铺购买了28个付费LLM API路由器,又从公开社区收集了400个免费路由器。他们在每个路由器上注册账户,让一个沙箱化的编码agent通过它运行,执行返回的每一个工具调用,并观察其行为。1
428个路由器中,有9个把工具调用改写成了攻击者控制的命令或依赖:付费的1个,免费的8个。另有17个免费路由器在传输中看到AWS金丝雀凭证后实际使用了它,还有1个从植入的以太坊私钥中转走了资金。实施注入的路由器中有2个隐藏了这种行为:一个等到50个请求之后才动手,另一个只在处于自主“YOLO mode”(自动批准工具执行的模式)且处理Rust或Go项目的会话中触发。1
LLM API路由器是一种应用层代理。它终止TLS,以明文读取每一个请求和响应,并且可以改写您的agent即将执行的工具调用。 论文没有发现任何主要供应商对工具调用响应进行签名,因此没有任何机制能把agent运行的命令与模型实际生成的内容绑定在一起。论文测试了四个agent框架,其中包括Claude Code和Codex,没有一个执行响应完整性检查。1
本文最初于2026年4月10日根据论文摘要发表。2026年10月2日,我依据论文全文重写了本文。四月版本有若干错误,列于文末附近的“四月版本的错误”一节。
TL;DR
- 路由器能做什么。 它作为您配置的端点,位于客户端与模型供应商之间,因此掌握每个请求和响应的明文,并能在转发前修改其中任何一方。1
- 428个路由器做了什么。 9个向返回的工具调用中注入了恶意代码(28个付费中1个,400个免费中8个)。其中2个免费路由器用触发条件加以隐藏。17个免费路由器使用了研究人员拥有的AWS金丝雀凭证,1个从研究人员拥有、余额不足50美元的以太坊私钥中转走了资金。1
- 泄露的密钥和薄弱的中继造成了什么。 一个故意泄露的OpenAI密钥承载了1亿个GPT-5.4 token,暴露了七个以上的Codex会话。配置薄弱的诱饵中继承载了约20亿个token,在440个Codex会话中暴露了99个凭证,其中401个会话本就在自动批准工具调用的状态下运行。1
- 防御措施的效果。 在作者自建的合成基准中,失败即关闭的策略闸门以1.0%的误报率拦截了全部注入样本;而在一个简单的自适应基准中,知道闸门存在的攻击者在100%的样本中绕过了它。异常筛查标记出89.0%的普通注入和约一半的规避型注入。1
- 作者认为什么能解决问题。 由供应商签名的响应信封,让客户端能够把工具调用与模型生成的内容进行核对。论文没有发现任何主要供应商提供这种机制。1
- 如果您自己托管路由器。 下文10月1日的更新涵盖了LiteLLM的11条安全公告,其中一条允许任何已认证用户让代理把其供应商密钥发送出去。2
核心要点
- Agent运营者: 位于客户端与模型供应商之间的每一个路由器都能以明文访问每个请求和响应,而客户端只能配置第一跳。如果您的路由器是从电商平台买来的,或是从公开列表中拿来的,在您有独立理由信任其运营者之前,请把它当作敌对的中间人。
- Harness构建者: PreToolUse钩子在工具调用执行之前运行,但此时路由器已经有机会改写这个调用了。钩子没有原始调用可供比对。它能做的是失败即关闭:只放行从列入清单的域名获取内容、只安装列入清单的软件包的shell命令,其余一律拦截。3
- 使用YOLO mode的人: 在研究人员的诱饵研究中,观察到的440个Codex会话里有401个本就在自动批准工具执行的状态下运行。1 对这些会话而言,无需任何触发逻辑,一条直接改写的命令就会被执行。不要让自动批准的会话经过您无法控制的路由器。
- 自托管代理的团队: 您自己运行的路由器同样集中了供应商密钥。2026年10月1日进入PyPA数据库的11条LiteLLM安全公告(大多数自6月起已公开)中,有一条允许任何已认证用户让代理把供应商密钥发送到其指定的主机。1.97.0及之后的版本不在全部11条记录的范围之内;详情见下文10月1日的更新。2
路由器到底是什么?
LLM API路由器以某种格式(通常与OpenAI兼容)接收请求,选择一个上游供应商,然后返回响应。论文把各种规模的路由器都计算在内:Amazon Bedrock和Azure OpenAI Service这样的云托管服务,LiteLLM和OpenRouter这样面向开发者的项目和服务,以及一个转售和聚合API访问权限的大宗商品市场。1
人们使用路由器有正当的理由。论文列举了“model fallback, load balancing, cost optimization, and a single API key across providers”(模型回退、负载均衡、成本优化,以及跨供应商的单一API密钥),并指出路由在“in regions where direct provider access is restricted, expensive, or subject to quota limitations”(直接访问供应商受限、昂贵或受配额限制的地区)尤为常见。1
问题出在位置上。按论文的说法,这里不需要任何拦截技巧:“the client voluntarily configures the router’s URL as the API endpoint, the router terminates the client-side TLS connection, and it originates a separate TLS connection upstream”(客户端主动把路由器的URL配置为API端点,路由器终止客户端一侧的TLS连接,再向上游另起一条TLS连接)。TLS保护每一段链路,却完全无法防止路由器接触载荷。路由器读取请求JSON并转发,读取响应JSON并返回,期间有机会修改其中任何一方。1
路由器还会串联。论文举的例子是:一名开发者从淘宝转售商处购买访问权限,转售商从二级聚合商处汇集密钥,聚合商再经OpenRouter路由,OpenRouter最终分发到模型托管方。一共四跳,每一跳都能完整访问明文。“The client configures only the first hop; subsequent hops are invisible.”(客户端只配置第一跳,之后的各跳都不可见。)任何一跳出了问题,整条路径都会被污染,而其后诚实的各跳无从察觉。1
当今的API中没有任何东西能弥补这一缺口。工具调用参数以明文JSON传输,而且“No provider-level integrity mechanism binds the arguments returned by the model to the arguments received by the client.”(没有任何供应商层面的完整性机制把模型返回的参数与客户端收到的参数绑定在一起。)1
测量是如何进行的
作者对每个路由器执行四个阶段。首先注册一个由研究人员控制的账户并获取API凭证。然后让该账户接入一个沙箱化的agent环境,运行“a randomized subset of custom tasks and TerminalBench 2.0 tasks”(从自定义任务和TerminalBench 2.0任务中随机抽取的子集),这些任务会产生贴近真实的shell、软件包安装和文件操作类工具调用。接着在沙箱内执行返回的每一个工具调用载荷,沙箱监视网络回连、文件系统变更和进程树异常。最后由另一个分析agent审查被标记的执行,把每一项归类为良性改写、可能恶意或确认恶意。1
这些定义是保守的。只有当“only if a returned tool-call payload is rewritten into an attacker-controlled command or dependency”(返回的工具调用载荷被改写成攻击者控制的命令或依赖)时,才算作路由器注入了恶意代码。当经过路由器的凭证“later produces follow-on AWS API activity attributable to that credential”(之后产生了可归因于该凭证的后续AWS API活动)时,才算作AWS金丝雀被触碰。1
研究样本是大宗商品市场,而不是整个领域。400个免费路由器以sub2api和new-api两个开源模板为主,作者表示企业级和仅限邀请的部署不在研究范围之内。样本大部分来自中文电商平台和社区。1
实地数据
| 行为 | 付费路由器(28) | 免费路由器(400) |
|---|---|---|
| 向返回的工具调用中注入恶意代码 | 1 | 8 |
| 用触发条件隐藏注入 | 未观察到 | 2 |
| 使用研究人员拥有的AWS金丝雀凭证 | 未观察到 | 17 |
| 转走研究人员拥有的以太坊私钥中的资金 | 未观察到 | 1 |
这些注入并非无关痛痒。论文指出,它们“change the command or dependency that the agent intends to execute”(改变了agent打算执行的命令或依赖)。付费路由器的结果是28个中有1个,作者给出了直白的结论:“Paid access improves service stability, but it does not prove tool-call integrity.”(付费访问提高了服务稳定性,但并不能证明工具调用的完整性。)1
两个带触发条件的路由器,是让测试失效的发现。其中一个“malicious tool calls are injected only after the first 50 requests, which hides the attack from shallow smoke tests”(只在前50个请求之后才注入恶意工具调用,从而躲过浅层的冒烟测试)。另一个只在“only for sessions that appear to be running in autonomous YOLO mode and only when the project fingerprint matches Rust or Go”(会话看起来运行在自主YOLO mode下,且项目指纹匹配Rust或Go时)才启用注入。作者补充说,他们看到的触发条件未必就是全部。他们只能通过有限的黑盒探测来观察这些路由器,因此“a router may implement additional latent conditions that our probes did not activate”(路由器可能还实现了探测没有激活的其他潜在条件)。1
凭证方面的发现全部来自免费路由器。17个路由器“trigger follow-on use of at least one researcher-owned AWS canary credential after seeing it in transit”(在传输中看到至少一个研究人员拥有的AWS金丝雀凭证后,触发了对它的后续使用),1个从预先充值的以太坊私钥中转走了资金。附录给出的这次转走造成的损失不足50美元。1
两项投毒研究
路由器不一定要怀有恶意,也可能落到同样的位置。作者通过两项研究考察了看似良性的路由器路径如何被投毒。
研究一:泄露的密钥。 他们把一个研究人员拥有的OpenAI API密钥泄露到“on Chinese forums, WeChat, and Telegram groups frequented by router operators”(路由器运营者常去的中文论坛、微信群和Telegram群组)。这一个密钥承载了1亿个GPT-5.4 token,暴露了七个以上的Codex用户会话,并且至少在一个会话中,其背后出现了多个不同的凭证。论文在流量如何到达这一点上很谨慎。该结果“is consistent with several paths: direct reuse by a router operator, direct reuse by unrelated third parties, or reuse somewhere inside a longer relay chain”(与多条路径相符:路由器运营者直接复用、无关第三方直接复用,或在更长的中继链中某处被复用),而作者“cannot always distinguish among those paths from provider-side logs alone”(仅凭供应商一侧的日志,并不总能区分这些路径)。这项研究确立的是影响半径:谁持有被复用的上游密钥,谁就能以明文看到其背后的流量。1
研究二:薄弱的中继。 他们在20个域名上部署了存在漏洞的claude-relay-service实例,在20个IP上部署了弱密码的Sub2API和CLIProxyAPI实例。这些诱饵吸引了来自147个IP的4万多次未授权访问尝试,作者将其解读为扫描与中继复用的混合,而并非每一次都是定向攻击。更强的信号出现在之后:这些诱饵“were folded into active agent-facing relay paths”(被并入了正在运行的面向agent的中继路径)。它们承载了约20亿个GPT-5.4和5.3-codex token,即约13 GB可见的提示词和响应流量,并在涉及398个项目或主机的440个Codex会话中暴露了99个不同的凭证。1
按论文的定义,这440个会话全部可以被注入命令,其中401个“already operated in YOLO mode, i.e., with tool execution auto-approved rather than confirmed command by command”(本就运行在YOLO mode下,即工具执行被自动批准,而不是逐条确认)。对于这401个会话,作者写道:“sophisticated adaptive evasion was unnecessary: basic payload injection would have been sufficient to change executed commands”(复杂的自适应规避并无必要:基本的载荷注入就足以改变被执行的命令)。1
这些都是别人的会话。诱饵被并入中继路径之后,这些人的流量便到达了研究人员的诱饵,而只配置第一跳的用户根本无从察觉。作者只保留了汇总元数据和加盐哈希,并在观察期结束时吊销了他们自己暴露的凭证。1
两种核心攻击类别和两种规避变体
论文把恶意路由器的行为归结为两种基本手法,以及对第一种的两种细化。1
AC-1:响应侧载荷注入。 路由器在工具调用离开供应商之后、到达客户端之前改写它,同时保持工具名称和schema不变。论文举的例子是一个Bash调用,其中的安装程序URL被替换成了攻击者的地址。由于结果对预期工具来说仍是合法的JSON,客户端看不出任何异常。
AC-2:被动窃取机密。 路由器什么都不改。它在请求和响应中扫描凭证模式,把找到的内容留存下来。流量没有被修改,因此“clients cannot observe router-side retention”(客户端无法观察到路由器一侧的留存)。暴露的范围不止API密钥:“system prompts, tool definitions, user-supplied file contents, and environment variables all transit the same plaintext channel”(系统提示词、工具定义、用户提供的文件内容和环境变量都经过同一条明文通道)。
AC-1.a:针对依赖的注入。 这是AC-1针对软件包安装命令的变体。路由器不替换URL,而是“substitutes a legitimate dependency name with an attacker-controlled package pre-registered on the target registry”(把合法的依赖名称替换成攻击者预先在目标仓库注册的软件包)。论文的例子把pip install requests变成了pip install reqeusts。命令仍然从受信任的仓库安装,所以只检查域名的闸门会放行它,而恶意软件包会留在缓存中,供之后的会话继续使用。
AC-1.b:条件触发投放。 这是决定AC-1何时触发的变体。路由器在触发条件匹配之前一直表现诚实。论文列出了五类触发条件:工具名称、内容关键词、YOLO mode之类的用户指纹、时间窗口和请求次数。“A router deploying a 50-request warm-up appears entirely transparent during a 10-request probe.”(设置了50个请求预热期的路由器,在10个请求的探测中看起来完全透明。)
论文把2026年3月的LiteLLM入侵事件列为大规模AC-1的例子:攻击者一旦控制了代理的请求管道,所有经过的工具调用都可能被改写。(论文把这次入侵描述为依赖混淆。我在供应链那篇文章中所依据的报道,则将其追溯到经由Trivy入侵窃取的PyPI发布令牌。)
研究代理揭示了agent客户端的哪些问题
作者构建了一个名为Mine的代理,实现了全部四类攻击,并让每个攻击模块分别向四个公开agent框架发送1,000个工具调用请求,这四个框架是OpenClaw、OpenCode、OpenAI的Codex和Anthropic的Claude Code。1
四个框架都没有验证响应完整性。被改写的工具调用在100%的AC-1案例和99.6%的AC-1.a案例中,以合法的框架原生格式到达客户端。论文对这一结果说明了什么、没说明什么讲得很精确:“These are compatibility measurements, not end-to-end execution rates: they show that the tampered tool call reaches the client in a valid framework-native format, but downstream permission prompts or local execution sandboxes may still block the resulting action.”(这些是兼容性测量,而非端到端执行率:它们表明被篡改的工具调用以合法的框架原生格式到达客户端,但下游的权限提示或本地执行沙箱仍可能拦截由此产生的操作。)1
正是这条限定说明了权限提示的价值所在,也说明了为什么自动批准在诱饵数据中如此关键。作者没有公开Mine。1
下一层的组合模式
我一直在写同一个结构性缺陷:各自获得授权的组件组合在一起,产生了未经授权的行为。从Trivy到LiteLLM是软件包层的组合。静默外泄是获取内容层的组合。MCP工具投毒是协议层的组合。
路由器攻击是客户端与供应商之间那一跳上的组合。您的客户端被授权调用路由器。路由器被授权调用上游模型。模型被授权作出响应。每一跳都经过授权,组合起来却依然产生了被改写的命令,因为没有任何一方对经过这一跳的内容签名;也产生了被复制的机密,因为每一跳都以明文读取它。
论文把这条线索延伸到了MCP本身:“a malicious MCP server receives tool-call requests in plaintext and can return forged results, so the same basic manipulation and collection ideas transfer with adaptation to the MCP message format”(恶意MCP服务器以明文接收工具调用请求,并能返回伪造的结果,因此同样的基本篡改和收集思路,经过适配后可以迁移到MCP消息格式上)。它也指出了二者的区别:MCP服务器位于工具执行一侧,能伪造工具输出;路由器位于通往模型的路径上,能看到每一个工具调用和完整的请求上下文。1
三种防御,附论文数据
论文评估了三种无需任何供应商协助、客户端即可部署的控制措施。下面所有数字都受同一条限定约束:“All numbers in this section come from controlled artifact-side evaluation rather than field data.”(本节所有数字均来自受控的制品侧评估,而非实地数据。)语料是合成的,由作者的脚本生成。1
| 防御 | AC-1 | AC-1.a | AC-1.b | 代价与局限 |
|---|---|---|---|---|
| 失败即关闭的策略闸门 | 拦截100% | 拦截100% | 未测量 | 误报率1.0%;在简单的自适应基准中,100%的样本绕过了它 |
| 响应侧异常筛查 | 标记89.0% | 标记50.0% | 标记50.9% | 误报率6.7% |
| 仅追加的透明日志 | 仅用于审计 | 仅用于审计 | 仅用于审计 | 每条约1.26 KB |
策略闸门拦截那些“fetch from non-allowlisted domains or install packages outside a local allowlist”(从不在允许清单中的域名获取内容,或安装本地允许清单之外的软件包)的shell命令。在1,000条良性命令、500个AC-1样本和500个AC-1.a样本上,它拦截了全部攻击样本,而所有误报都是从允许清单遗漏的镜像域名获取安装程序。作者称之为“the strongest immediately deployable control”(可立即部署的最强控制措施)。他们也如实报告了它的失效方式:如果攻击者把载荷托管在允许清单中的域名上,或者预先放置一个本地脚本,之后由一条看似无害的命令来运行,那么“the policy gate is bypassed in 100% of samples”(策略闸门在100%的样本中被绕过)。作者的结论是,这个闸门“should be understood as a coarse but useful filter for obvious high-risk workflows, not as a robust defense against defense-aware attackers”(应被理解为针对明显高风险工作流的粗糙但有用的过滤器,而不是对抗了解防御的攻击者的可靠防线)。1
对构建闸门的人来说有一个关键细节,而论文在这一点上前后不一致。第4节把闸门描述为只检查域名,并专门设计AC-1.a来击败它:“the gate blocks non-allowlisted domains but does not maintain a per-package allowlist”(闸门拦截不在允许清单中的域名,但不维护逐个软件包的允许清单)。而第7节中对AC-1.a取得100%成绩的闸门,同时也拦截“outside a local allowlist”(本地允许清单之外)的安装。依我的理解,没有软件包清单的闸门会完全漏掉这种拼写仿冒式的替换。
异常筛查根据本地可见的特征(例如shell风险模式和参数熵)为每个返回的工具调用打分,没有上游原始调用可供比对。在6.7%的误报率下,它标记了89.0%的AC-1样本、50.0%的AC-1.a样本和50.9%的AC-1.b样本。在1%的误报预算下,它只抓到43.5%的AC-1,对AC-1.a几乎一个都没抓到。作者评价它“useful for review prioritization”(适合用来排定审查优先级),效果弱于闸门。1
透明日志记录请求、响应、路由器URL、TLS元数据以及原始响应的哈希。它什么也阻止不了,但能让您在事件发生后回答:某个路由器或某个凭证波及了多远,哪些会话经过了它。1
作者呼吁的修复方案
这三种控制措施都无法认证工具调用的来源。作者自己也这么说:“No client-side control available today can prove that a router preserved the upstream provider’s response.”(当今没有任何客户端控制措施能够证明路由器保留了上游供应商的响应。)1
能做到这一点的是供应商的签名。论文提出了“a provider-signed canonical response envelope, similar in spirit to DKIM for email”(一种由供应商签名的规范化响应信封,思路类似于电子邮件的DKIM),涵盖模型标识符、工具名称、工具参数、结束原因和客户端nonce,客户端在执行任何工具调用之前先验证它。论文报告目前无人提供这种机制:“To our knowledge, none of the major provider tool-use APIs or the current MCP specification expose a deployed response-signing mechanism for tool-call arguments today.”(据我们所知,目前主要供应商的工具使用API和现行MCP规范都没有为工具调用参数提供已部署的响应签名机制。)1
这一提议有两个局限。传输安全无法替代它:双向TLS和证书固定“can authenticate the router endpoint the client chose, but they do not say whether the returned tool call preserves upstream semantics”(能够认证客户端所选的路由器端点,但无法说明返回的工具调用是否保留了上游语义)。签名对被窃取的机密也无能为力:“AC-2 cannot be mitigated by response-signing proposals because the secrets are exposed on the request path before any provider-side mechanism can act.”(响应签名方案无法缓解AC-2,因为机密在任何供应商侧机制起作用之前,就已在请求路径上暴露。)1
您实际上应该做什么
如果您的agent通过一个并非您自己构建的路由器调用模型:
- 能直连就直连,不能直连就弄清运营者是谁。 加一个路由器只需要改一下base URL,正因如此,它常常在没有任何决策的情况下就被加上了。请把它变成一项决策。这里的“信任”指的是外部依据,例如一个您了解的团队、一份合同,或一个您能依法追责的司法管辖区。电商平台上的评价不算。
- 对高风险工具调用实行失败即关闭。 在Claude Code中,这就是一个PreToolUse钩子,拦截从允许清单之外的域名获取内容的shell命令,以及允许清单之外的软件包安装。务必保留软件包清单:依赖替换这一变体的存在,就是为了击败只检查域名的闸门。把钩子写成拒绝一切无法解析的内容,使用退出码2或
deny决定。Claude Code把其他退出码和超时都视为不拦截,因此崩溃或卡住的钩子不会阻止调用:调用会继续进入正常的权限流程,在自动批准的会话中就会直接运行。还要确认钩子在第一次调用时确实运行了。文档警告说:“a mistyped path in settings.json leaves the gate silently disabled”(settings.json中的路径写错,会让闸门在无声无息中失效)。要做好长期维护这两份清单的准备,也要预料到执意攻击的人会设法绕过它们。3 - 绝不要让自动批准的会话经过您无法控制的路由器。 诱饵研究中的401个会话就是前车之鉴。在被改写的工具调用与其执行之间,权限提示是为数不多的屏障之一。
- 不要让机密进入流量。 被动收集不会改变任何您能观察到的东西。提示词、工具结果或agent读取的文件中的任何内容,都会以明文经过路由器。把凭证的权限范围收窄,尽量不让它们进入上下文,凡是经过了您后来起疑的路由器的东西,一律轮换。
- 在本地记录日志。 记录请求、响应、路由器URL和响应哈希,先从请求中抹去机密,再存放在路由器够不到的地方。它不能阻止攻击,但能在事后告诉您哪些内容被暴露了。
- 在沙箱中执行。 论文指出,沙箱“reduce post-execution blast radius but do not authenticate where a tool call came from”(能缩小执行后的影响半径,但无法认证工具调用的来源)。前一半的好处要拿到手。
令人不安的暗示
路由器层清楚地说明了agent生态系统交付基础设施的速度快于保障其安全的速度。人们想要一把钥匙通用所有模型、更低的价格,以及在供应商不提供服务的地区也能访问。路由器把这三样都给了他们,市场也为此给予回报。
同样的过程已经在MCP层、软件包层和获取内容层上演过。agent技术栈出现了新的一层。在有人审计之前,开发者就已采用。攻击者来了,然后研究人员才来。这一次,研究人员统计到428个路由器,其中9个注入恶意代码,17个使用了植入的凭证,1个掏空了钱包,另有401个自动批准的会话流经包含研究人员诱饵的中继路径。1
能够弥补这一缺口的那块拼图,即由供应商签名的响应,不是运营者自己能加上的。在供应商提供之前,上面的控制措施可以降低暴露,但没有一项能证明某个工具调用确实出自模型本身。
更新,2026年10月1日:您自己托管的路由器也在名单上
本文讲的是别人运行的路由器。安全公告记录补上了另一半。2026年10月1日,PyPA安全公告数据库新增了11条针对LiteLLM的条目。LiteLLM是一个开源代理,团队出于上文所说的理由(一把密钥通用所有模型)自行托管。2 这些条目没有一条是新的。其中9条自6月21日起就已在GitHub安全公告数据库和NVD中,第10条自9月中旬起就在;而对代理来说最要紧的那一条,因为它会暴露供应商密钥,于8月26日在LiteLLM自己的仓库中发布,修复版本自8月9日起就已在PyPI上,GitHub将其评为Moderate。这些条目值得放在一起读,一是因为它们反映了这一层的状况,二是因为8月9日之前发布的每一个正式版本都在CVE-2026-84377的影响范围之内。
最先要读的是CVE-2026-84377。用仓库公告的原话说:“Any authenticated LiteLLM proxy user could redirect an outbound provider call to a destination they control and cause the proxy to send its own configured provider credentials to that destination.”(任何已认证的LiteLLM代理用户都可以把发往供应商的出站调用重定向到自己控制的目的地,并让代理把其自身配置的供应商凭证发送到该目的地。)原因在于检查方式的形态:“The proxy’s request-body validation was a denylist that did not cover every sensitive parameter and did not inspect parameters nested inside other request fields.”(代理的请求体校验是一份拒绝清单,没有覆盖所有敏感参数,也不检查嵌套在其他请求字段中的参数。)修复覆盖了九条发布线,从1.88.6到1.96.2。对于暂时无法升级的人,公告在一句话里列出了三种缓解办法:“Set general_settings.allow_client_side_credentials to false so callers cannot override connection parameters, restrict proxy keys to trusted callers, and block the affected parameters (api_base, base_url, model_list, fallbacks, provider credential fields) at a reverse proxy or API gateway.”(把general_settings.allow_client_side_credentials设为false,使调用方无法覆盖连接参数;把代理密钥限制给受信任的调用方;并在反向代理或API网关处拦截受影响的参数。)2 仅靠第一条是不够的。在受影响版本1.95.0的源码中,只有当该设置为true时才会完全跳过请求体检查(部署的configurable_clientside_auth_params仍可豁免单个参数),所以从未改动过这个设置的安装本来就处于false,而那个不完整的检查正是公告所描述的缺陷。真正的修复是升级。6
第二条,CVE-2026-59823,是同一缺陷的缩小版:防护“blocks the api_base and base_url parameters but does not cover user_config”(拦截api_base和base_url参数,却没有覆盖user_config),因此持有有效虚拟密钥的调用方可以把api_base放进其中,让代理指向任意主机。这个问题在1.83.9中修复,该版本自4月17日起就在PyPI上;公告则在9月才发布。4
有两条涉及代理的MCP部分:MCP代理中的不当认证(CVE-2026-12773,其版本范围以1.84.0中的修复为终点),以及通过MCP OpenAPI规范加载器的spec_path参数实施的服务端请求伪造(CVE-2026-12798,记录为影响1.82.2及以前的版本)。其余七条涉及管理员密钥处理、SSO调试流程、SSO会话失效、生成密钥的会话过期、UI中的用户枚举、异步端点上的护栏绕过,以及机器对机器的JWT处理。5
这九条记录比前两条单薄。它们的描述用的是VulDB的模板化措辞,而不是维护者的说明;MCP代理那一条的描述写着“up to 1.59.8”(直到1.59.8),版本范围却写着在1.84.0中修复。5 CVE-2026-84377的记录也有自己的出入:仓库页面列出的受影响版本是“<1.94.0”,而经审核的记录中的范围却覆盖了1.96线,直到其修复版本1.96.2。2
请对照您实际运行的版本核查这些范围,而不要轻信任何摘要,包括本文。所有记录的范围都终止于1.96.2或更低版本,因此自8月16日起在PyPI上的1.97.0及之后版本,不在全部11条的范围之内;10月1日的最新版本是1.103.2。5
无论由谁运营,路由器的危险都在于凭证所在的位置。一个触碰植入AWS密钥的电商平台路由器,和一个可以被诱导把供应商密钥发送出去的自托管代理,是从两个方向抵达的同一种暴露:一个经由运营者,另一个经由任何持有虚拟密钥的租户。上文各节的防御措施都假定您是客户端。
对于您自己运行的代理,还要补上运营者这一半:把每一个虚拟密钥都当作通往其背后上游密钥的凭证;除非确有需要,关闭由客户端提供的连接参数,包括按部署设置的configurable_clientside_auth_params;并让代理和其他所有持有机密的系统遵循同样的补丁节奏。
如果在代理运行受影响版本期间,有任何您并不完全信任的人持有虚拟密钥,请轮换供应商密钥以及代理上配置的其他所有机密,并检查日志中是否有发往您未配置主机的出站调用;公告的影响范围涵盖“other configured secrets”(其他已配置的机密)以及发往“internal services reachable from the proxy”(代理可达的内部服务)的请求。11条中有两条,即MCP代理中的CVE-2026-12773和SSO调试流程中的CVE-2026-12795,是1.84.0以下版本中的认证缺陷,因此在这些版本上,谁持有密钥未必能限定谁可以访问代理;如果代理可以从您不信任的网络访问,请同样进行轮换。
四月版本的错误
本文4月10日的版本是根据论文摘要写成的。2026年10月2日对照全文重读后,发现以下几处有错误或具有误导性,均已在上文更正:
- AC-1.a。 我把它描述为一种“only fires when the request matches a specific dependency or context”(只在请求匹配特定依赖或上下文时触发)的注入。实际上它是安装命令中的软件包名称替换。触发条件属于AC-1.b。
- 防御措施。 我写道“the abstract does not rank the defenses”(摘要没有对防御措施排序),并把一个排序当作我的个人看法提出。论文正文测量了全部三种措施,称策略闸门为“the strongest immediately deployable control”(可立即部署的最强控制措施),并报告它在一个简单的自适应基准中被100%的样本绕过。这一点我当时漏掉了。
- 关于签名的建议。 我建议运营者在客户端对请求签名、在上游验证,并称之为“the only real fix”(唯一真正的修复)。论文要求的方向正好相反:由供应商签名、由客户端验证响应,并且指出签名无法解决机密被窃的问题。
- 泄露的密钥。 我说该密钥是“as if it had been exposed through a developer mistake”(仿佛因开发者失误而暴露)那样被泄露的,并得出结论“The router was a laundering layer for a stolen key.”(路由器是被盗密钥的洗白层。)实际上,它被泄露到了路由器运营者分享凭证的论坛和聊天群组中,而论文表示无法总是分辨复用它的是路由器运营者、无关第三方,还是更长的中继链。
- 凭证数量。 答案块写的是“17 of 28 paid routers touched planted AWS credentials”(28个付费路由器中有17个触碰了植入的AWS凭证),描述则说研究人员测试了28个路由器。论文中付费那一行没有观察到任何凭证滥用。使用AWS金丝雀的全部17个路由器,以及转走ETH的那1个,都属于400个免费路由器。
- 关于钩子的建议。 我推荐了“validate response shapes”(验证响应结构)的PostToolUse钩子。PostToolUse钩子在工具执行之后才运行,对被改写的命令来说为时已晚。论文测试的控制措施是执行前的闸门,在Claude Code中就是带允许清单的PreToolUse钩子。
- 较小的错误。 论文有六位作者,而不是五位。论文并没有说路由器“knows when it is being sampled”(知道自己正在被抽样检测),那是我的渲染。开头把主题称为“MCP trust chains”(MCP信任链),而论文研究的是路由器,不是MCP。我还把静默外泄那篇文章描述为关于工具描述的文章,而它实际讲的是隐藏在获取内容中的指令。
常见问题
在这里,LLM API路由器指的是什么?
一种以统一格式(通常与OpenAI兼容)接收请求、选择上游模型供应商并返回响应的服务。它是一个应用层代理,能以明文访问每个请求和响应。1
TLS能保护我免受恶意路由器的侵害吗?
不能。客户端把路由器配置为自己的端点,因此路由器会终止客户端的TLS会话,再向上游另开一个会话。TLS保护每一段链路,却完全无法防止路由器接触载荷。1
如何检测正在改写工具调用的路由器?
靠测试无法可靠地检测。研究中有两个路由器只在50个请求之后,或只针对Rust或Go项目中自动批准的会话才实施注入,论文的结论是“no fixed-length client test can guarantee that the router is benign”(任何固定长度的客户端测试都无法保证路由器是良性的)。对shell命令和软件包安装采用失败即关闭的允许清单,可以拦截简单的情况,而论文也表明了解防御的攻击者能够绕过它。1
PreToolUse钩子有用吗?
有用,作为策略闸门。钩子看到的是客户端收到的工具调用,如果路由器改写过,那就是改写后的版本;它可以拦截从未列入清单的域名获取内容或安装未列入清单软件包的命令。但它无法判断这个调用是否就是模型生成的内容。13
我让Claude Code直连api.anthropic.com,会受影响吗?
不会受到这篇论文中路由器攻击的影响,因为中间没有中介。如果您出于任何原因让Claude Code经过代理,例如公司网关或模型聚合服务,那么这个代理就处于同样的位置。
OpenRouter、LiteLLM或其他知名聚合服务呢?
论文测量的是来自三个电商平台的28个付费路由器,以及主要基于两个开源模板构建的400个免费路由器。它只把LiteLLM和OpenRouter作为背景提及,并没有测试它们。结构性的结论适用于任何路由器:它能读取并改写流量,而可见性与完整性是两种不同的属性。对于您自己托管的代理,上文10月1日的更新涵盖了11条LiteLLM安全公告。
那401个自动批准的会话属于谁?
属于流量到达了研究人员诱饵中继的第三方。如果您让自动批准的agent会话经过一个并非您自己构建的路由器,请立即停止,轮换经过它的每一个凭证,并检查会话日志中是否有您未预料到的工具调用。
参考文献
-
Hanzhi Liu、Chaofan Shou、Hongbo Wen、Yanju Chen、Ryan Jingyang Fang和Yu Feng,“Your Agent Is Mine: Measuring Malicious Intermediary Attacks on the LLM Supply Chain”,arXiv:2604.08407v1,2026年4月9日,列入ACM Conference on Computer and Communications Security(2026年10月)。全文于2026年10月1日和2日阅读。所用章节:Introduction和2.1(路由器是什么、四跳示例、TLS终止);2.2(不存在供应商层面的完整性机制);4.1和4.2(四类攻击、
requests到reqeusts的示例、五类触发条件);5.1(四阶段流程及各项定义);5.2以及Table 3和4(1个付费和8个免费路由器实施注入、2个免费路由器带触发条件、17个免费路由器使用AWS金丝雀、1个转走ETH);5.3(泄露的密钥和诱饵:1亿token、七个以上Codex会话、来自147个IP的4万多次访问尝试、约20亿token、约13 GB、99个凭证、440个会话、398个项目或主机、401个处于YOLO mode);5.4(主要发现,包括关于付费访问的那句话);5.5(范围);6和Table 5(Mine、四个框架、每个模块1,000个请求、100%和99.6%的兼容性);7和Table 6(三种防御及其结果);8.2和8.3(签名响应信封、MCP);9(MCP服务器与路由器所处位置的区别);Appendix A(数据留存、凭证吊销、不足50美元的转走金额、Mine未公开)。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
LiteLLM仓库安全公告GHSA-3cv6-jpf6-8222(CVE-2026-84377,PYSEC-2026-4066),“Authenticated SSRF and provider-credential exfiltration via unvalidated request-body routing parameters”,2026年8月26日在仓库发布,9月2日由NVD收录,9月30日经GitHub审核;2026年10月1日通过仓库页面和OSV记录阅读。影响说明和完整的Workarounds句子引自该公告;OSV记录的Patches一行为“Fixed in 1.96.2, 1.95.1, 1.94.3, 1.93.2, 1.92.2, 1.91.5, 1.90.7, 1.89.7, and 1.88.6”,仓库页面则列出“Affected versions <1.94.0”。11条这一数字来自PyPA安全公告数据库中PYSEC-2026-4066至PYSEC-2026-4076的条目,全部针对
litellm软件包,日期均为2026年10月1日,均未撤回。 ↩↩↩↩↩ -
Anthropic,Hooks reference,Claude Code文档,2026年10月2日获取。PreToolUse在“Before a tool call executes. Can block it”(工具调用执行之前,可以拦截)时运行;PostToolUse在“After a tool call succeeds”(工具调用成功之后)时运行;“Any other exit code doesn’t block on its own for most hook events”(对大多数钩子事件而言,其他退出码本身不会拦截);超时的命令钩子“doesn’t block the tool call”(不会拦截工具调用);以及“a mistyped path in settings.json leaves the gate silently disabled.”(settings.json中的路径一旦写错,这道闸门就会在毫无提示的情况下处于停用状态。) ↩↩↩
-
GitHub安全公告GHSA-hx8v-g79f-8w5f(CVE-2026-59823,PYSEC-2026-4070),“LiteLLM Proxy has server-side request forgery via the
user_configrequest parameter”,2026年9月17日发布,2026年10月1日通过OSV阅读:受影响版本<= 1.83.8,修复版本1.83.9,公告将该版本的发布日期记为2026年4月17日。 ↩ -
2026年10月1日阅读的OSV记录:PYSEC-2026-4067(CVE-2026-12773,MCP代理认证)、PYSEC-2026-4069(CVE-2026-12798,MCP OpenAPI规范加载器),以及PYSEC-2026-4068、4071、4072、4073、4074、4075和4076。这九条背后的GitHub安全公告均于2026年6月21日发布,即NVD收录它们的同一天,且每条都引用了VulDB。上传日期和当前版本取自同日阅读的PyPI项目页面及其JSON:1.88.6至1.95.1于8月9日,1.96.2于8月11日,1.97.0于8月16日,1.103.2于10月1日。 ↩↩↩
-
作者对PyPI上
litellm1.95.0 wheel包的阅读(受影响范围为1.95.0,直到1.95.1中的修复),2026年10月1日:在litellm/proxy/auth/auth_utils.py中,当general_settings.get("allow_client_side_credentials") is True时,_check_banned_params不拒绝任何内容就直接返回;否则,它会拒绝请求体中带有禁用清单参数的请求,除非部署的configurable_clientside_auth_params允许该参数。该版本的禁用清单包括vertex_ai_credentials以及可观测性工具的凭证和主机。我只读了一个受影响的版本,并未读全部九条发布线。 ↩