[控场AI]
· 9 分钟阅读· 4,929 字

OpenRouter Armor评测:Agent工具调用拦截的三种模式与Claude Code的漏洞

OpenRouter Armor评测:Agent工具调用拦截的三种模式与Claude Code的漏洞

OpenRouter Armor 用模型为工具调用把关,但 Claude Code 模式下只标记不拦截。

OpenRouter 推出 beta 版 Armor,在模型与应用之间插入一个安全分类器,用自然语言规则检查每次工具调用。它基于 TypeSafe 的 Jev 结构化决策模型打分,规则分 hard_deny、soft_deny、allow 三层,deny 默认阈值 0.6,并提供 audit 模式供先观察后拦截。在 Chat Completions 和 Responses 上,被拦截的调用不会到达代码;但在 Messages 的 claude_code_compatibility 模式下,Armor 只把调用标记为 flagged,拦截责任转移给客户端——忽略 safeguard_results 的自研客户端会直接执行被标记的 tool_use。此外 on_error 的 block/log 设置管不到该模式,unavailable 判定一律放行,零数据保留等场景也不在 Armor 覆盖范围内。加载 Claude Code 规则集后延迟中位增加 279ms,且每次工具调用都要付出这笔成本。

Agent 自作主张删掉一个你从未提及的文件夹,问题出在模型和 shell 之间缺少一道「说不」的闸门。OpenRouter 在 10 月 15 日推出 beta 版 Armor,想做这道闸门:一个位于模型和应用之间的安全分类器,用自然语言规则检查每一次工具调用。被标记的调用不会执行,模型会被告知自己撞了哪条规则。

但文档里藏着一个例外,而且恰好出现在最贴近 Claude Code 的场景里。

OpenRouter Armor 工具调用安全分类器

Armor 的判定机制:用模型而不是正则

对每一次工具调用,OpenRouter 会把调用内容、你写的规则和对话上下文一起发给一个决策模型,由它返回该调用匹配每条规则的概率,再汇总成一个 flagged 或 not_flagged 的结论。

OpenRouter 在发布帖中点名了背后的模型:TypeSafe 的 Jev。这是一个结构化决策模型,返回带概率的类型化答案,而不是自由生成的文本,当前版本为 typesafe/jev-1.14。

为什么用模型而不是规则列表?OpenRouter 的说法是「基于正则的过滤往往不足以识别不想要的工具调用」。发布帖还声称 Armor 在性能和成本上都优于 Claude Code 自带的 auto-mode 分类器。不过,explainx.ai 的一篇评测指出,OpenRouter 并未公开这套结论背后的测试集和数字。这是一个合理的质疑——安全类的性能宣称,没有可复现的评测就只是营销话术。

结构化决策模型是一类专门为分类、路由或打分任务训练的模型,输出的是带类型的结构化结果(如枚举标签加置信度概率),而不是自由生成的文本。与通用 LLM 相比,它通常更小、更快、更便宜,且输出格式稳定,便于程序直接解析。OpenRouter 选用 TypeSafe 的 Jev 正是看中这一点:每次工具调用都要在毫秒级完成判定,且结果需要可比较的概率值来与阈值对照。这类模型的能力边界也由此确定——它擅长按给定规则做判断,但不具备通用推理能力,规则写得含糊时它的表现会明显下降。

三层规则:hard_deny、soft_deny 与 allow

规则体系分为三层:

  • hard_deny 用于「无论用户说什么都绝不能发生」的动作;
  • soft_deny 用于「通常不该做、但有时确实是合理请求」的动作,命中后除非有 allow 规则同时匹配,或用户在对话中明确要求了对该目标执行该操作,否则阻止;
  • allow 本身从不阻止任何东西,只用于定义 soft-deny 的例外。

Armor 还内置了一条 soft-deny,针对用户在对话中设定且尚未解除的限制,比如「我审阅之前不要 push」。每条规则包含 id、可选的 name、instructions 和可选的 threshold,deny 规则默认阈值 0.6,allow 规则默认 0.7。

一个实用的细节是 "mode": "audit":此时 Armor 对每次调用打分但不阻止任何东西,适合先用真实流量调规则,再切换为强制执行。注意 audit 模式不能与 claude_code_compatibility 组合使用。

被拦截的调用去哪里:三种 output_mode

拦截结果的呈现方式取决于 output_mode:

output_mode支持的 API被拦截调用的形态
text(Chat Completions 与 Responses 默认)Chat Completions、Responses助手文本中出现 <openrouter_armor_blocked> 标记
custom_itemResponses一个 openrouter:armor_blocked 输出项
claude_code_compatibilityMessages保留 tool_use,在 safeguard_results 中标记为 flagged

前两种模式下,被拦截的调用根本不会到达你的代码,模型会被告知该调用「被 OpenRouter Armor 按某条规则阻止且未执行」,并被要求不要用相同参数重试。

第三种模式的行为完全不同,也是本文标题所指的核心问题。

Chat Completions、Responses 和 Messages 是当前 LLM API 的三种主要请求格式。Chat Completions 是 OpenAI 最早普及的对话补全接口,生态最广;Responses 是 OpenAI 较新的接口,用统一的输入/输出项(item)模型表达文本、工具调用和多轮状态;Messages 则是 Anthropic 的接口格式,Claude Code 等工具基于它构建。三者的差异直接决定了 Armor 能把拦截结果塞进哪里:Chat Completions 只有文本通道,所以只能用 <openrouter_armor_blocked> 标记;Responses 有结构化的输出项,可以新增 openrouter:armor_blocked 类型;Messages 的 tool_use 块是客户端自己解析执行的,因此 Armor 只能附加 safeguard_results 供客户端参考,而无法代替客户端拒绝执行。

Claude Code 模式:只标记,不拦截

一个携带 dangerous_tool_use 类型 safeguards 条目的 Messages 请求会自动选中这种模式,无需 plugins 配置。文档的措辞很直白:「在 claude_code_compatibility 下,阻止调用的是你的应用。」在文档描述的流程中,Claude Code 就是这个应用:它读取 flagged 结论,拒绝执行,并写回一个 is_error 的工具结果。

漏洞在于:如果你自己写了一个 Messages 客户端,而它从不读取判定结果,被标记的 tool_use 就会被直接执行。文档明确警告:「忽略 safeguard_results 的 Messages 客户端会运行被标记的 tool_use。」如果无法确认客户端会检查,官方建议改用 "output_mode": "text"。

如果你确实要自研 Messages 客户端,执行前必须检查。safeguard_results 是顶层数组,每个条目的 status.tool_uses 以工具调用 id 为键,包含 type、outcome 和可选的 explanation。一个稳妥的做法是:只有当 Armor 确实评估过且结论为 not_flagged 时才执行,缺失判定或判定为 unavailable 一律拒绝。需要说明,拒绝缺失判定是笔者的选择,并非文档强制要求,但它与 Armor 插件请求中 on_error 的默认值方向一致。

故障时闭还是开:on_error 的边界

决策模型可能超时、报错,或收到大到无法判定的请求,此时结论为 unavailable,由 on_error 决定行为:block(默认)或 log。但这个设置管不到 Claude Code 模式——在那里,unavailable 的调用总是被放行并附带可见判定,由客户端自己决定。

其他几个值得注意的边界条件:

  • Armor 尚不支持 HIPAA、零数据保留(Zero Data Retention)或数据区域限制下的请求。这些场景的插件请求会返回 403,而 Claude Code 请求则被放行并标记 unavailable,回退到 Claude Code 本地的 auto-mode 检查。换言之,在零数据保留场景下,今天并不是 Armor 在把关。
  • 决策模型会产生误报和漏报,per-rule 的 threshold 是调节旋钮。
  • 加载 Claude Code 兼容规则集时,Armor 的中位延迟增加 279 ms,P90 增加 334 ms。
  • beta 期间评估不计费,但 OpenRouter 表示正式发布前会定价,且配置字段、safeguard_results 对象和 openrouter:armor_blocked 项类型都可能变动。

第三点是每次工具调用都要付的成本。一个 agent 回合里连续触发多次工具调用,这笔延迟会叠加。

on_error 的 block 与 log 对应分布式系统中的 fail-closed 与 fail-open 两种故障策略。fail-closed 指依赖组件不可用时默认拒绝,安全性高但可用性受损;fail-open 则默认放行,保证业务连续但把风险暴露给下游。对 agent 安全检查而言,选择取决于误伤成本与漏放成本的对比:block 会在决策模型抖动时中断正常任务,log 则可能在检查器故障的窗口期内让危险调用通过。值得注意的是,这两种策略都只是转移风险而非消除风险,实际部署时通常还需要配合审计日志,以便在 fail-open 期间事后追溯。

通过 Ori 运行 Claude Code

Armor 的 Claude Code 路径经由 Ori,这是 OpenRouter 的启动器,能把它支持的 agent CLI 运行在 OpenRouter 上。它用 OAuth 登录而不是粘贴密钥,支持 Claude Code、Codex、OpenCode、Cline 等。安装脚本是 curl -fsSL https://openrouter.ai/labs/ori/install.sh | bash——在把它管道进 shell 之前,建议先读一遍脚本内容。

之后用非 Anthropic 模型加 auto 模式启动:

ori claude --model moonshotai/kimi-k2-0905 -- --permission-mode auto

Claude Code 的 auto 模式会用第二个模型来审查动作。据 GIGAZINE 报道,经由 Ori,OpenRouter 上的模型可以使用这套审批机制,由 Armor 承担审查角色。

上锁之前的三件事

想试 Armor,先想清楚这三点:

  1. 从 "mode": "audit" 开始,在真实流量上读判定结果,确认规则合理后再允许拦截。
  2. 在 Chat Completions 和 Responses 上主动选择 on_error。block 让检查器故障时不放行,log 让检查器故障时不阻断 agent,取决于你的风险偏好。
  3. 如果你拥有 Messages 客户端,在每次 tool_use 前检查 safeguard_results,或干脆改用 "output_mode": "text"。

Armor 本质上是在 agent 的每个动作前插入一个网络跳转,外加一个故障时闭还是开的决策。这会带来延迟预算、超时处理和依赖不可用时的系统行为问题——这些正是分布式系统设计的经典议题,只不过现在落在了 agent 的安全检查上。

一个值得权衡的问题:你愿意为 agent 的每次工具调用多付出约 300 毫秒,换取一层由第二个模型把守的检查吗?还是更信任权限提示和你自己的眼睛?

#OpenRouter Armor#Agent 工具调用安全#Claude Code#AI Agent 安全#tool_use 拦截#TypeSafe Jev#AI 编程安全
分享:

相关推荐