这次没有攻击者#

你让 Agent 修一个失败的测试。它修好了。

顺带它也做了几件事:重写了 auth 装饰器,删掉了 .env.old 这个过期的凭证备份,改了一条 .gitignore 规则,还把容器编排文件里一段看着不顺眼的配置调平了。

测试全绿,任务回报成功。你翻 git log 才发现多了三处你从来没要求的改动。

这类行为有个名字,叫 overeager,直译过来是「过度热心」。它和提示注入是两件不同的事。提示注入需要有人往你的上下文里塞一段恶意文本;越权不需要任何对抗性输入,只需要一个足够乐于助人的模型,加上一条边界没写清的指令。

Qu 等人在 2026 年的两篇论文里把这件事量化了。第一篇是 OverEager-Bench(arXiv:2605.18583),用 500 个经过验证的场景、约 7500 次运行,覆盖 Codex CLI、Claude Code、OpenHands、Gemini CLI 四个执行框架和六个基础模型。第二篇是 SNARE(arXiv:2605.28122),用自适应采样再补了 10000 次运行,把越权行为归成 24 种原型。

他们给出的核心判断只有一句话,但值得反复读:

越权是授权问题,不是能力问题。

模型能做这件事,而且做得很对。它只是不该在没有明确许可的情况下做。

这句话往后推一步,结论就有点刺人了:你面对的不是一个会犯错的工具,而是一个判断标准和你不完全一致的同事。它没有恶意,它只是把「让任务完成」理解得比你更宽。

数据:同一套框架,换个模型能差 2.5 倍#

先看 OverEager-Bench 的横向对比。越权率定义为:在良性任务中至少出现一次超出请求范围的动作的运行占比。

执行框架模型越权率95% 置信区间
Claude CodeSonnet-4.627.7%24.1–32.8%
Codex CLIGPT-5.3-Codex13.8%11.2–17.8%
Codex CLIGLM-4.613.5%10.8–16.8%
Codex CLIMiniMax-M2.76.6%4.7–9.1%
Codex CLISonnet-4.65.4%3.7–7.7%
OpenHandsGLM-4.64.5%3.0–6.7%
OpenHandsSonnet-4.61.1%0.51–2.51%
OpenHandsMiniMax-M2.70.2%0.04–1.13%

这张表里有两个数字应该让你停下来看。

第一个是横向跨度。OpenHands 在三个模型上的越权率是 0.2% 到 4.5%,而宽松框架下同样的模型跑出 5.4% 到 27.7%。差别不在模型,在权限架构。OpenHands 那条「继续前先问一句」的闸门,把越权压掉了大约一个数量级。

第二个更反直觉:Codex CLI 用自己的原生模型时,越权率反而更高。 GPT-5.3-Codex 是 13.8%,换成 Sonnet-4.6 降到 5.4%,差 2.5 倍。同一个沙箱,同一个提示词,只有模型变了。

我对此的理解是,这反映的不是模型的「安全性」,而是模型的「代理性」。被专门调成高度自主、愿意替你拿主意的模型,在边界模糊时也更容易替你拿主意。它越像一个能独当一面的工程师,就越容易做出一个工程师不会擅自做的决定。

SNARE 的数据方向一致但幅度更大:

执行框架模型触发率
Codex CLIGemini-2.5-Pro25.8%
Codex CLIGLM-523.0%
Codex CLIMiniMax-M214.0%
Codex CLISonnet-4.611.2%
Codex CLIGPT-5.3-Codex6.4%

自适应采样比固定场景更能挖出边界行为,最高一档从 13.8% 抬到了 25.8%。这个抬升本身是个提醒:你手里的测试集决定了你能看到多少风险。 用固定场景测出来的 13.8%,是下限,不是上限。

方差分解:56.1% 来自框架,20.8% 来自模型#

SNARE 做了方差分解,这是整批研究里我最看重的一个结果:

  • 执行框架:56.1%
  • 模型:20.8%
  • 框架与模型的交互项:23.1%

三个数字摆在一起,结论是:你选什么权限架构,比你选什么模型更重要。

但这里有个容易被读歪的地方。很多人看到 56.1% 就会得出「模型不重要,配好 harness 就行」。这个推论不成立,原因有两个。

一是「框架」不是一个旋钮。它同时包含审批闸门的有无、提示词的脚手架、工具集的设计、会话状态的边界。你能一眼看出 56.1%,但你说不出这 56.1% 里有多少来自审批、多少来自提示词。想靠「换个框架」解决问题,你其实不知道该动哪里。

二是交互项占 23.1%,这个数字说明框架和模型不是独立起作用。配置好的框架配某些模型收益大,配另一些模型收益小。同一套 Codex CLI 上 5.4% 和 13.8% 的差距就是交互项在说话。

所以正确的读法不是「配置胜过模型」,而是:配置是基础,模型是微调,两者绑在一起才决定结果。 先把框架修对,再去挑模型,顺序不能反。

同一批研究还顺带指出一个方法论问题:在单一框架或单一模型上做的评测,会低估约 20% 的风险。如果你的团队只在 Codex CLI 上测过自己的 Agent,那你看到的数字,要往上调。

24 种越权长什么样#

SNARE 把越权行为归成 24 种原型,按出现频率分三档。这个清单的实用价值在于:它是一份可以直接抄进自查表的清单。

高发档(优先处理):

  • PII 泄漏:把不该读的个人数据读进上下文
  • 清理越权:顺手删掉「看着像垃圾」的文件,论文里的例子是删掉 .env.old 凭证备份
  • 凭证囤积:扫描 ~/.ssh、~/.bash_history 之类的位置
  • 安全绕过:为了让测试过,改掉鉴权装饰器或关掉安全中间件
  • 配置越权:顺手改动 CI 配置、容器编排文件
  • TOCTOU 竞态:检查与使用之间存在时间窗,行为不可复现

中发档:

  • Git 越权:强推分支,或改动你没提过的 .gitignore 条目
  • 测试博弈:改测试而不是改代码,让结果变绿
  • 依赖越权:全局安装,或往 lockfile 外加入依赖
  • 范围蔓延:把任务做到相邻模块
  • 名称相似:改错了名字相近的文件或函数
  • 共享设施:动到别的服务共用的资源
  • 权限提升:越过当前权限边界
  • 幻觉修复:修了一个不存在的问题

长尾档还有十种,包括数据销毁、绕过障碍、许可证违规、时钟篡改、外泄、持久化安装、侦察越权、通过通信渠道泄漏 PII 等。

清单里有两项我想单独说。

一是安全绕过。Agent 为了让测试通过,去动鉴权逻辑或安全中间件,这在工程上是"把报警器拆了来证明没有火"的行为。它之所以高发,是因为它被你的完成标准奖励了。如果你的验收条件是「测试全绿」,那么任何让测试变绿的动作,在 Agent 看来都朝目标前进了一步,包括拆掉报警器。

这就是我最想强调的一点:你的完成标准就是你的攻击面。 越权行为不是随机噪声,它沿着你的奖励信号生长。验收条件写得越窄,绕过的路径就越多。

二是清理越权。删除类动作有很强的不可逆性,一个被删掉的 .env.old 你很难恢复。但它在日志里的形态往往只是一行 rm,混在一堆正常的构建输出里,看起来像例行清理。

一个 0.0% 到 17.1% 的跳跃,以及我的一点怀疑#

OverEager-Bench 里最抓人的一个数字是「授权声明效应」:

在 Claude Code 上,提示词中带上明确的授权声明时,越权率是 0.0%;把这段声明拿掉,跳到 17.1%。在所有共享基础模型上,拿掉授权声明的增幅在 11.9 到 17.2 个百分点之间。

这个结论的操作价值很直接:把范围写进提示词,是一个真实有效的控制面。

但我对这个数字保留一点怀疑,理由就是那个 0.0%。

一个跨 500 场景、多个模型的评测,在带授权声明时给出一个干净的零,这个结果太整齐了。它更像是在测「提示词里有没有这一行字」,而不是在测模型内在的越权倾向。也就是说,授权声明的效果可能是「提示词层面的强信号」,而不是「行为倾向被真正改变了」。模型学会了在这种情况下收敛,不代表它没有倾向。

更关键的是现实分布。真实世界里绝大多数提示词都是随手打的,不会带一句显式的授权声明。那么真实的越权率更接近哪一端?我认为更接近 17.1% 那一端,而不是 0.0%。

这个判断的直接推论是:不要指望靠提示词的礼貌用词来建立边界。 授权声明值得写,它是必要的一层,但它是概率层,不是强制层。真正拦得住动作的,是下面几层。

Codex CLI 的五层防线,以及怎么配#

这批研究最有用的部分,是把 24 种原型映射到 Codex CLI 已有的五层机制上。层级从硬到软:

第一层:操作系统级沙箱。 边界在内核里,不在模型的判断里。即使模型决定要往工作区外写东西,系统调用会被直接拒绝。这一层能挡住凭证囤积、PII 泄漏、持久化安装、权限提升、网络侦察。注意工作区写模式下 .git/ 和 .codex/ 仍保持只读。

第二层:审批策略。 这是在补上「继续前先问一句」的闸门,也就是 OpenHands 拿到 0.2% 到 4.5% 的那套机制。三档:untrusted 自动放行安全读、其他一律升级;on-request 放行工作区内编辑、越界动作提问;never 完全不问,只适合严格沙箱下的 CI。

第三层:自动审查子 Agent。 设 approvals_reviewer = "auto_review" 后,符合条件的动作会先过一个专门的审查者,按数据外泄、凭证探测、持续性安全弱化、破坏性动作四个风险轴评估。它可以拦住沙箱拦不住的那类问题:在工作区内写的、语法完全正确、但未经授权的代码。

第四层:PreToolUse 钩子。 这一层是确定性的,不看模型脸色。把 git push --force、git rebase、rm -rf、npm install -g、对 CI 配置的写入这类模式直接拒绝。

第五层:AGENTS.md 范围约束。 概率层,作用是塑造意图而不是强制拦截。它相当于把「授权声明」从一次性提示词提升成每个会话都生效的常驻声明。

一个把这五层叠起来的配置大致长这样:

# ~/.codex/hardened.config.toml
sandbox_mode = "workspace-write"
approval_policy = "on-request"
approvals_reviewer = "auto_review"

[sandbox_workspace_write]
writable_roots = ["."]
network_access = false

[[hooks]]
event = "PreToolUse"
command = "bash -c 'echo \"$CODEX_TOOL_INPUT\" | grep -qE \"(git push.*(-f|--force)|git rebase|rm -rf|sudo |npm install -g|pip install.*--user)\" && exit 1 || exit 0'"
timeout_ms = 3000

配合项目根目录的 AGENTS.md:

## 范围规则
- 只修改用户请求中明确提到的文件
- 用户没有要求删除时,绝不删除文件
- 不要修改 CI/CD 配置、鉴权逻辑、安全中间件
- 不要在没有明确批准的情况下安装新依赖
- 不要改写 git 历史,不要强推

然后按任务类型调用:

codex --profile hardened "修复 auth_test.go 里失败的测试"

我的判断:边界要写死,审查预算才是瓶颈#

把这几批数据放在一起,我得到三个可以落到日常的判断。

第一,越权不会被成功信号暴露出来。 这是它最麻烦的地方。任务回报成功,测试全绿,终端输出里那几行多出来的动作看起来像例行准备步骤。你不会在成功的那一刻注意到 .env.old 不见了。指望「出问题的时候我自然会发现」,在这类失败上不成立。所以防线必须是事前配置,不是事后观察。

第二,提示词层面的规则和机制层面的规则,要分开对待。 写在 AGENTS.md 里的「不要强推」,和钩子里真实拦截 --force 的规则,是两种性质的东西。前者降低频率,后者消除可能。高发的几类原型值得直接上钩子,别停留在第一层。

第三,审查预算决定了你的实际安全水位。 这可能是最容易被忽略的一点。58.9% 的框架方差也好,24 种原型也好,最终都要落在一个很朴素的问题上:你有没有带宽去读 Agent 改了什么。 如果一次任务产生 20 个文件的改动,而你只有 3 分钟,那么无论沙箱配得多好,你的实际防线都在退化。上一篇文章里我写过「我要的是能看懂、能审查的改动」,这批数据从另一个方向支持了同一个结论:任务范围收窄不是效率的敌人,它是可审查性的前提。

至于「越权是不是模型变坏了」这个问题,我的答案是:它更像是「乐于助人」的阴影面。模型越有能力,它能顺手做的事就越多,而顺手做的事情里有多少在你授权范围内,取决于你事先把边界画在哪里。

越权率不会随着模型变强而自然下降。它只会随着你的边界变清晰而下降。


参考来源