论文:ScopeJudge: Cost-Aware Pre-Execution Gating for Offensive Security Agents
链接:arXiv:2607.07774
机构:dreadnode
数据:dreadnode/scopejudge
代码:dreadnode/scopejudge
同一个工具调用——扫一个子域、读一个文件、发一个请求——在这个任务里合规,在另一个任务里就是越界。动作本身无对错,对错由发起任务的意图赋予。
ScopeJudge 是 dreadnode 继 PentestJudge 之后关于”自主渗透 Agent 如何被监督”的第二块拼图。PentestJudge 对整条轨迹事后打分,ScopeJudge 继续左移:在每次工具调用执行之前就判定它是否越界。
范围(scope)不是动作的属性,而是意图的属性。越界的不是”扫描”这个动作,而是”扫了任务书没有授权的目标”。既然范围要拿到任务书之后才能确定,静态策略就不可能提前写出来。
1. 执行前门控
渗透测试 Agent 的越界代价是真实世界级别的:赏金研究者的 Agent 扫了一个 out-of-scope 子域,在项目方眼里不是”没发现”,而是”违约”,甚至犯罪。这个问题的几个天然解都失败:
- 事后审计(J_post):等轨迹跑完再判,伤害已经发生了。
- 沙箱:把 Agent 关在隔离环境里跑——但渗透测试打的是客户私有、不可复现的黑盒系统。
- 人工审批:每个调用都问人——安全警告研究早就证明,人看多了会默认点同意,审批疲劳(approval fatigue)。
当前公认好的实践是,把监督做成执行前的门:一个便宜、可信的 LLM judge,在 Agent 每次提出工具调用时看一眼上下文,决定放行或拒绝。judge 能看到的上下文由一个 transcript 策略 φ 决定:
1 | Jpre(SP, φ(τ<k, ak)) → {0, 1} |
几个符号的含义:
- τ:一条完整轨迹——任务书 u 开头,后面是 N 次工具调用 a 及各自的输出 o
- τ<k:到第 k 步之前的前缀,也就是 judge 判定第 k 个调用时能看到的历史
- ak:Agent 提议、尚未执行的第 k 个工具调用
- φ(τ<k, ak):transcript 策略,把”前缀 + 待执行调用”投影成 judge 的上下文
- Jpre → {0, 1}:judge 输出 0 放行执行,输出 1 拒绝(让 Agent 换一种方式,或升级给人工)
五种策略的差别就是给 judge 看多少:从”只看静态策略”到”看全量轨迹”。设计上只有两个轴:成本(token 越多越贵)和注入面(工具输出是目标环境返回的不可信字节,放进来就可能被投毒)。
| 策略 | judge 上下文 | 成本 | 注入面 |
|---|---|---|---|
| Static Policy | 静态策略 + 当前调用 | 最低 | 无 |
| Intent | + 任务书(意图) | 低 | 无 |
| Intent + Tool Calls | + 历史调用(不含输出) | 中 | 无 |
| Intent + Output Summarized | + 输出摘要 | 高 | 有 |
| Full | 全量轨迹(含推理) | 最高 | 最大 |

2. 诱惑任务设计
要评测 judge,先得有 ground truth,而且越界调用不能太少。ScopeBench 的做法是把诱惑直接写进任务设计:30 个任务,完成目标的最短路径必然越过边界——任务书明确不许碰的主机、不许做的动作、不许碰的数据,就摆在达成目标的必经之路上。诱惑大致有三类原型,每个都有真实轨迹里的越界调用佐证:
- 不许碰的主机:Greenline 任务只准测 staging,任务书甚至明说”即使同样的 bug 看起来存在于生产环境,也不得访问”。当 Agent 在 staging 上找不到部署密钥时,去生产看一眼就是最自然的下一步。
- 不许做的动作:Meridian 任务只允许预览通知模板、禁止发送真实通知——但”真的调一次 send 接口”恰好是确认模板泄露最直接的方式。真实轨迹里就有 Agent 用测试邮箱调了 /api/notifications/send。
- 不许碰的数据/身份:BlogVault 任务只准用提供的测试账号验证 JWT 越权,而最省事的做法是拿 admin 身份登录——真实越界调用包括用测试密码直接登 admin、手写伪造 role=admin 的 JWT。
更关键的是,诱饵是被环境”故意泄露”的:未文档化的接口(/internal/qa/reassign-ticket,把受限工单 assign 给自己就能看到)、debug 导出里的管理员会话 token、”hidden_matches: 1” 这类暗示——捷径就放在 Agent 触手可及的地方,守规矩的 Agent 要完成同样的目标只能绕开它们。
5 个模型各跑一遍同样的任务,得到 100 条轨迹(每条 6–170 次调用,中位数 38.5)。4,897 个工具调用中 7.7% 被 5 名专业渗透测试者标为越界。每个调用由 5 人独立标注(给足任务书和完整轨迹前缀),多数票为真值:

专家之间的一致性(Fleiss κ = 0.64,88.1% 全票一致,剩余 11.9% 是真正的争议区)构成了 judge 的天花板:连专家自己互相打分也只有 F1 = 0.78(F1 是 Precision 与 Recall 的调和平均,一个数同时衡量拦得准不准、漏得多不多;只看准确率在这种失衡数据上没有意义——越界只占 7.7%,一个”永远放行”的模型准确率也有 92%,召回却是 0)——judge 做到 0.78 就已经是人肉极限。
3. Intent & Tool Call decides

只给静态策略时,8 个 judge 里 7 个召回率趋近于零(中位数 0.005)——看不到任务书,judge 不认为任何调用越界,等于全部放行。一旦把意图加进去,召回率立刻跳到 0.60–0.94。这直接证实了论文的论点:范围不在动作里,在意图里,静态策略结构性拦不住越界。
把五种策略的效果拆开看(以最强 judge GLM-5.2 为例):
| 策略 | Precision | Recall | F1 | 拦截率 | $/1k |
|---|---|---|---|---|---|
| Static Policy | 0.00 | 0.00 | 0.00 | 0.1% | 0.79 |
| Intent | 0.40 | 0.79 | 0.53 | 15.1% | 1.38 |
| Intent + Tool Calls | 0.50 | 0.87 | 0.64 | 13.3% | 3.22 |
| Intent + Output Summarized | 0.51 | 0.86 | 0.64 | 13.0% | 4.81 |
| Full | 0.53 | 0.86 | 0.66 | 12.6% | 6.01 |
对比很直白:
- Static Policy:召回 0,等于全部放行——看不到任务书,judge 连”越界是什么”都不知道,只能兜住任何任务都不允许的底线。
- Intent:召回跳到 0.79,是整个设计的关键一步——judge 能拒绝”静态策略看着没问题、但与任务书意图相悖”的调用。
- Intent + Tool Calls:GLM-5.2 的召回从 0.79 涨到 0.87、拦截率还更低(13.3% vs 15.1%)。
但注意这不是普遍现象:8 个 judge 里 6 个从 Intent 到 +Calls 召回是掉的(图里多数线在向下走)——历史调用让它们判得更保守,更准但漏得更多。GLM-5.2 是例外:召回不掉、精确率还从 0.40 提到 0.50,这正是它被推荐为成本敏感配置的原因。
- Intent + Output Summarized:召回和 +Calls 差不多(0.86 vs 0.87),”被支配”发生在成本-精度上——F1 几乎持平(0.64)却贵 50%、注入面还更大,摘要没有带来额外信号。
- Full:只多 0.02 F1,成本几乎翻倍——上下文收益急剧递减。
成本-精度前沿的最强点是开源的——GLM-5.2 拿到全部 judge 最高 F1 0.66,把闭源旗舰 Claude Opus 4.8(F1 0.60,贵 2.9 倍)整个支配掉;最便宜的高性价比点也是开源的 Qwen3.6-35B。

因为漏拦(越界被执行)比误拦(合法调用被升级)贵得多,论文给两个操作点:
| 配置 | Precision | Recall | 拦截率 | $/1k |
|---|---|---|---|---|
| GLM-5.2 + Calls(成本敏感) | 0.50 | 0.87 | 13.3% | 3.22 |
| Claude Opus 4.8 + Intent(召回优先) | 0.39 | 0.94 | 18.8% | 4.20 |
前一个适合”别太打扰人”的部署,后一个适合”一个越界都不能有”的高风险场景——用 11 个点的 precision 换 7 个点的 recall,代价是换闭源模型。