CheckerBench 基准:会修漏洞的 Agent 写不出可复用检查器

一个已经能读懂 Linux 内核补丁、能定位空指针解引用的编码 agent,被要求做一件听起来只多走半步的事。把"这类漏洞"写成一条检查器,让分析器以后自动扫出同类问题。它交出的东西编译通过,对示例也能报出告警。可它一拿到真实代码库的主干上,就开始满屏误报,或者对真正的漏洞视而不见。得分,不及格。

2026 年 10 月,论文 CheckerBench 把这件事做成了一场可重复的考试。300 项任务,21 组模型与 harness 配置,每种组合跑 3 次,一共 63 次运行。平均 Pass@1 只有 32.30%,最好的一组配置 45.33%。而同一批运行里,检查器"能编译通过"的比例高达 78.25%。会修漏洞的 agent,为什么写不出一条能长期复用的检查器?这是全篇要回答的问题。CheckerBench

写检查器,到底比修一个漏洞难在哪?

先弄清"检查器"是什么。日常写代码时用的 linter 规则就是它。那是一段不运行程序、只靠扫描代码结构就能发现某类问题的逻辑,比如"每个可能返回空的函数,返回值都要判空"。它和一次性检测的区别在于,检查器要跑在成千上万个文件上,而不是对一段给定代码判一次。

2025 年发表于 SOSP 的 KNighter 给过一个具体例子。一个内核补丁修复了 devm_kzalloc 返回值未判空导致的空指针解引用。KNighter 从补丁里读出漏洞模式,合成出一条 Clang Static Analyzer 检查器,再用这条检查器去扫描整个内核,结果在别处发现了一处新的同类漏洞,编号 CVE-2024-50103。KNighter

图注:三联图。(a) 是修复 devm_kzalloc 空指针漏洞的内核补丁,(b) 是合成出的检查器在别处新发现并标记为 CVE-2024-50103 的漏洞,(c) 是检查器本身的 CSA 代码。它把"端到端合成一个检查器"从抽象名词变成三格看得见的画面。KNighter

这张三联图点出了难度所在。修一处漏洞,是把一道具体的错题改对。写一条检查器,是造一套判卷标准,它要判对整类题目,还要避免把没错的题误判为错。这两件事的能力要求不在一个层级。检查器必须被分析器基础设施接受,能从源码重新编译,能在"修复前有告警、修复后没告警"的差分中翻转结论,还要在主干的真实代码上足够精准。任何一环松掉,它就是一个不能用的东西。

这条链路天然是长程任务。agent 要反复读代码、写检查器、编译、看分析器反馈、再改,不是一问一答能完成的。KNighter 把它做成两阶段流水线。第一阶段从补丁抽取缺陷模式、生成检测计划、实现检查器,并带一个编译驱动的修复循环;第二阶段把检查器部署到内核上扫描,用一个 triage agent 判断每条告警是真是假,误报就交给 refinement agent 去修改检查器逻辑。CheckerBench 给每道题的合成预算是 50 分钟的活跃生成时间,足够跑出几十上百步工具调用。它考的不是单点能力,而是一整段持续的、有反馈的推进过程。

KNighter 证明了这件事可行,却把自己绑死在单一仓库和单一分析器上,Linux 内核加 Clang Static Analyzer,合成轨迹还是为自己定制的,别人没法拿同一套题去比较不同的 agent。它留下一个缺口:能不能造一场跨分析器、跨语言、可比较的考试?

论文把现有评测的空白归纳成四类。第一类是不以检查器为中心,SWE-bench 那样的 issue 修复、ReposVul 那样的漏洞分类,都没有要求 agent 交出一件可反复复用的检查器。第二类是视野有限,RhoSynth、MoCQ 这些方法把流程拆成预先定好的阶段,反而掐死了 agent 边走边规划、边实现边调试的空间。第三类是绑定单一系统,各家都用自己定制的 harness,成绩之间没法横着比。第四类是正确性信号碎片化,现有指标测的是补丁对不对、测试过没过,而不是分析器兼容性、误报控制、缺陷覆盖这些检查器独有的性质。把这四类空白凑在一起,就能看出为什么之前没人能回答"agent 到底会不会写检查器"这个问题。

要把"写检查器"变成一场可比较的考试,题该怎么出?

CheckerBench 的答案是拿真实的历史漏洞出题。公开漏洞库 NVD 里的每条 CVE 记录都能连到一个修复提交,而修复提交天然给出"脆弱版本"和"修复版本"这对锚点。有了这对锚点,检查器有没有真的抓到漏洞就有了客观判据,不必靠人工打分来猜。

论文用一个六阶段流水线,把原始 CVE 打磨成可评测的任务。

图注:CheckerBench 六阶段构造流水线,依次是漏洞来源收集、环境重建、任务打包、验证器构造、人工引导 rollout、质量控制,下方用 FFmpeg 案例示意一对脆弱与修复版本如何被打包成带固定环境和冻结验证器的任务。CheckerBench

前两个阶段确定题目和复现环境。系统从 NVD 收集 CVE 并连到修复提交,然后在隔离工作区里重建脆弱与修复两个版本,装好固定版本的工具链,C/C++ 用 Clang Static Analyzer,另外四种语言用 CodeQL。第三阶段打包任务,这一步是方法论的关键之一。系统给任务一个随机的工作区编号,剥掉 CVE、CWE 这些会泄露答案的标签,再配一个共享技能库。技能库里有 harness 使用技巧、分析器文档、示例检查器和一些语义工具,唯独没有针对当前任务的解法。第四阶段构造验证器并把它冻结。第五阶段收集人工引导的校准轨迹,每个任务一次初始尝试加最多两次重跑,单次至少 40 步交互。第六阶段做完整性检查和人工复核,并保证同一仓库、同一 CVE 的任务留在同一个数据划分里。

这几步里藏着出题人反复权衡的地方。剥掉 CVE 和 CWE 标签,是为了不让 agent 直接在记忆里检索答案。技能库里放通用工具却不放解法,是为了把"会不会用分析器"和"会不会解这道题"分开。把同一仓库、同一 CVE 的任务关进同一个划分,是为了防止测试集被训练集间接污染。校准轨迹用真人引导跑出来,先确认每道题在合理努力下确实可解,否则一道根本做不出来的题只会拉低所有人的分数,什么信息也不提供。

其中最重要的设计是第四阶段的验证器,它在合成阶段对 agent 完全隐藏。agent 拿不到参考答案,必须自己造出一件能被独立重建、并跑出正确结论的工具。这像一场闭卷考试,不许你偷看标准答案,考完之后还有人会拿着你交上来的那把尺子,在另一个房间里重新量一遍。有了这把藏起来的尺子,"看起来合理"就不再能骗过评测。

流水线图下方的 FFmpeg 案例展示了一次真实的打包。同一个文件在某个提交前后的两个版本被分别放进固定好的环境,剥掉编号和标签后交到 agent 手上。它看到的只是"这个仓库里有某类缺陷,请你写一条检查器",看不到那是哪条 CVE,也看不到修复补丁。任务的可复现性和防泄漏,就靠这些约束同时达成。

这套流水线产出的数据规模如下。

维度数量
任务300
覆盖 CVE297
仓库167
CWE 类别85
语言生态5(C/C++、Java、Python、JavaScript、Go)

按语言拆开,C/C++ 有 159 项,Python 51 项,Java 30 项,JavaScript 30 项,Go 30 项。仓库里 Linux 独占 107 项,FFmpeg 和 ImageMagick 各 4 项。出现最多的三类缺陷是 CWE-787 越界写、CWE-125 越界读和 CWE-22 路径穿越。这里有个容易被忽略的偏斜:C/C++ 占到 53%,各语言的分母差别很大,语言之间的排名不能等权看待。

这道考试真正的难点不在题量,而在参考答案被藏了起来。

怎么判断检查器真的能用,而不是 Agent 自以为能用?

题目出好了,判分更难。agent 常常会自信地报告"我的检查器能工作",所以判分的第一原则是:它说能用不算数,必须在干净环境里从源码重建,并真的跑出结论。

图注:CheckerLab 工作流。agent 在隔离沙箱里拿到任务、工具和 50 分钟预算,提交后检查器被冻结,验证器在干净环境中独立重建它,再对脆弱与修复两版代码做诊断对比、补丁定位和主干误报扫描,最后由判官模型对过程质量、漏洞语义和抽样告警打分。右侧是 TorchServe 试点轨迹中的五个阶段。CheckerBench

这套工作流把一次运行拆成九道判定,而且它们是"与"的关系,不是求平均分。九项分别是:构建成功;目标达成;判官通过;主干误报率不超过 25%;过程分至少 80 分;语义分至少 7 分,CodeQL 任务要 8 分,满分 10 分;机制匹配;语义正确性分类到位;没有硬编码或锚定到补丁。任何一项不过,整次运行归零。过程分和语义分由独立的判官模型给出,主干误报则来自抽样扫描。

评测同时记录三个中间指标。Build 表示检查器能不能编译并注册进分析器。Diff. SR 表示它的诊断能不能随着修复而翻转,也就是"修复前有告警、修复后没有"。Pass@1 则是九道判定全部通过。编译通过只是入场券,语义、误报、过程质量任何一项不及格,都算没过。把它们理解成一条串联的判定链,就能明白为什么 Pass@1 会远远低于 Build。

为什么不直接给检查器打一个平均分,而要串起九道判定?因为检查器的价值在于"能不能用",而不是"用起来有多好"。一个编译不过的检查器,或者一个在主干上到处乱报的检查器,无论其它维度多出色都等于零。九道判定把这种"一票否决"写进了评分,也让"部分正确"无法被平均掉。这和让 agent 自己写测试来证明自己能工作有本质差别。自己报告成功,只要挑一个刚好能触发的样例就能自我认证;而独立重建加上主干误报扫描,把认证权从生成者手里拿走了。

63 次运行考下来,最好配置也只过 45.33%,卡在哪里?

论文让 7 个模型分别配上 3 种 harness,每种组合重复 3 次,共 21 组配置、63 次配置级运行。这里有两层不同的统计单位,先分清楚。一次配置级运行,是某一组模型加 harness 把整个 300 项任务集跑完一遍;而记录单道题成败的更细一层单位,是这次评测内部的单任务尝试,63 次配置级运行乘上 300 项任务,一共 18,900 次任务尝试。下文报的通过率和失败数,除非特别说明,都落在任务尝试这一层。harness 指 agent 与计算机之间的那层界面,包括它能调用哪些工具、环境状态怎么回传、能不能多轮迭代。它不是一个可有可无的实现细节。

图注:21 组模型与 harness 组合在 300 项任务上的 Pass@1 横向柱状图,按 harness 分组。Claude Opus 4.8 配 OpenCode 以 45.33% 居首,最低的几组落在 21% 到 25% 之间。分组画法让读者一眼看到同一模型换 harness 会左右移动。CheckerBench

平均 Pass@1 是 32.30%(按单任务尝试统计),最好的一组是 Claude Opus 4.8 配 OpenCode,45.33%。同一批评测里,Build 达到 78.25%,Diff. SR 达到 73.65%。看落差就更清楚了。从 Build 到 Diff. SR 只掉 4.60 个百分点,而从 Diff. SR 到 Pass@1 掉了 41.35 个百分点。也就是说,agent 大多能编译出检查器,甚至能让诊断随修复翻转,但走到真正合格还差得很远。最极端的一组是 GPT-5.6 配 Hermes Agent,Build 100%、Diff. SR 64%,Pass@1 却只有 23%。构建成功和使用价值在这里几乎完全脱钩,这个脱钩本身就是一个值得停下来看的反常信号。

再看失败模式。论文按原因归类了 4,360 次失败的任务尝试,单位仍是任务尝试,而不是前面那 63 次配置级运行。

失败模式占比
预算耗尽25.32%
合成过程不足22.96%
语义建模不足21.15%
误报过多15.96%
差分定位失败8.17%
过拟合到补丁3.56%
产物缺失1.83%
编译或注册失败1.05%

编译失败只占 1.05%,而过程与语义两类加起来超过四成。瓶颈不在"能不能编译"。预算耗尽占到四分之一,说明不少任务尝试在 50 分钟里根本没做完。过程与语义两类失败各自超过两成,说明做完了的那些也常常做得不对。三条放在一起,一个反直觉的结论就浮出来了:拖后腿的不是模型不会用工具,而是它在有限预算里既没规划好整条合成路径,也没能把缺陷语义写对。

harness 的影响同样明显。三种 harness 的平均 Pass@1 分别是 OpenCode 36.95%、Claude Code 33.81%、Hermes Agent 26.14%,最高和最低差了 10 个百分点以上。同一个模型换一个 harness,成绩能移动这么多,所以分析的单位应该是"模型加 harness"的组合,而不是单看模型。这个想法并不新。SWE-agent 早就把 agent 与计算机之间的界面设计当成一件正经事来研究,提出 Agent-Computer Interface 的概念,用一个专门的中间层取代裸 shell,并证明这一层能让同一个模型的任务成绩明显变化。SWE-agent 后来 OpenHands 又把它推进成带沙箱运行时和事件流的平台,harness 从研究原型变成了平台级抽象。OpenHands

这不只是说"工具很重要"。它意味着,报告一个模型的编码能力时,如果不说明它跑在哪个 harness 里,这个数字就是半成品。论文因此把模型与 harness 的组合当成分析单位。对做评测的人来说,这是一个可操作的结论:想让比较站得住,就得把界面和工具一起固定下来。另外,每种配置都跑了 3 次,得到的不是一个点而是一小簇结果,这也提醒读者别把单个格子之间的细微差别太当回事。真正稳的信号,是上面那几个跨越多组配置的落差。

32% 放回 2026 年的长程智能体里,算什么水平?

CheckerBench 不是凭空冒出来的。它的评分范式可以追溯到 SWE-bench:把真实仓库的 issue 变成任务,让模型生成补丁,再用真实测试判断补丁是否解决了问题。agent 加 repo、Pass@1、fail-to-pass 测试,这一套后来成了所有长程编码 benchmark 的共同底座。SWE-bench CheckerBench 只是把任务从"交一份补丁"换成了"写一条检查器",它是这条谱系的同族成员,而不是一个孤立的新基准。

放到更长的时间和更多基准里看,45.33% 并不是 CheckerBench 一家撞到的天花板,而是 2026 年长程编码 agent 共同落进去的区间。SWE-Bench Pro 面向企业仓库,公共子集最好成绩 43.6%,商业子集所有模型都低于 20%。SWE-Bench Pro RoadmapBench 做的是跨版本升级,最好的模型解掉 39.1%,最差的只有 5.2%。它的任务中位数要改动 3,714 行、涉及 51 个文件,规模远超 SWE-bench 的一条 issue 补丁。RoadmapBench 把这几条线摆在一起,一个 5% 到 45% 的通过率走廊就出来了。

这条走廊恐怕不是巧合。它更像一个信号:当任务长到需要上百步工具交互、需要在跨文件甚至跨模块的范围里保持一致时,当前模型的自验证和长程规划能力就一起顶到了同一堵墙。不同 benchmark 只是从不同角度撞上去而已。

顺带一提,走廊的位置会随模型进步整体上移。SWE-bench 最初报告的基线只有 1.96%,如今同族基准上的好成绩已经高得多。真正没被这轮进步解决的,是长期任务的完成率。走到一半的能力大幅提升,走到终点的能力仍然稀缺。

图注:Long-Horizon-Terminal-Bench 的排行榜,17 个模型在 R≥0.9、R≥0.95、R=1.0 三个阈值下的通过率加平均奖励。Grok 4.5 在 R≥0.95 时最高约 28.3%,而阈值收紧到 R=1.0 时多数模型直接归零,显示部分得分与严格通过之间的巨大落差。LHT-Bench

其中最有意思的对照来自 Long-Horizon-Terminal-Bench。它也是长程任务,单次运行平均 88.9 分钟、消耗约 980 万 token,预算比 CheckerBench 的 50 分钟还多出接近一倍,但平均通过率只有 6.4%,最好也不过 28.3%。LHT-Bench 预算更长、成绩反而更低,这个反差值得解释。一个合理的说法是反馈信号的差异。CheckerBench 给的是编译器和分析器的反馈,能具体告诉 agent 哪里不对、下一步该改什么;纯终端任务更多只能靠最终成败来判断,信息量小得多。CheckerBench 的 32% 平均值因此显得不那么低,它换来的是一类更有信息量的中间反馈。

不过这一切的前提,是它测的东西确实不一样。CheckerBench 要的是造一件能被别人反复使用的工具,而不是交一份一次性的补丁。这也正是它区别于同族 benchmark 的地方。

同族的几个 benchmark 其实还都暴露了同一个结构:中间进度远高于最终完成。RoadmapBench 里 Claude Opus 4.6 在 32.2% 的攻克率下,加权完成分已经到 0.627;CheckerBench 里 Build 有 78% 而 Pass@1 只有 32%。两处都在说同一件事,agent 能完成长任务里的很多片段,却很难把它们推进到一个能被独立验证者接受的终态。CheckerBench 的特别之处,在于它把这条鸿沟放在一个专门造工具的任务上测量,而不是放在一次性改动上。

卡住 Agent 的,是工具用不熟,还是缺陷没读懂?

回到那个核心问题。从数字看,答案偏向后者。

编译类失败只占 1.05%,工具调用本身并不是主要障碍,因为 agent 早就熟练地在多轮里调用工具。真正卡人的是"语义建模不足"和"合成过程不足",两类加起来超过四成。论文给了一个例子。一个本应做路径敏感分析的检查器,退化成简单的语法匹配,漏掉了加锁和控制流条件。agent 知道怎么写一段能在分析器里跑起来的代码,却没把"这个缺陷为什么是缺陷"翻译成分析器能执行的逻辑。

这还引出另一个现象:agent 容易过拟合到"编译干净"。Build 成功率很高,Pass@1 却很低,说明它在追求"能编过",而不是"缺陷语义正确"。再加上主干上的误报控制,误报过多仍是主要的失败原因之一(占失败任务的 15.96%)。有意思的是,对误报阈值的敏感性却很弱。把门槛从 10% 调到 25% 再到 50%,聚合 Pass@1 只在 32.05%、32.30%、32.83% 之间小幅移动。这说明卡点不在阈值设得松还是紧,而在检查器本身的判断质量。

消融实验从侧面印证了各部分的贡献。

DeepSeek-V4-Pro 配 OpenCodePass@1
完整配置39.67%
去掉技能库8.33%
去掉执行反馈13.33%
去掉 harness0.33%

完整配置的 Pass@1 是 39.67%,去掉技能库掉到 8.33%,去掉执行反馈掉到 13.33%,连 harness 都不要、退化成一次性生成则只剩 0.33%,300 项任务里有 237 项直接交不出有效产物。论文提醒这些数字跨越了不同的运行条件,只能当作方向性证据,但方向很清楚,技能库、执行反馈、harness 缺一不可。顺带说,时间预算的曲线也很陡。聚合 Pass@1 从 10 分钟的 7.24% 升到 30 分钟的 26.05%,再到 50 分钟的 32.30%,而 40 分钟的截断点就已经覆盖了 50 分钟成功案例的 93.02%。这说明成功需要一段不短的持续迭代,但收益在接近终点前就基本兑现完了。

需要说明的是,这仍是一篇 2026 年 10 月刚提交的 arXiv v1,还没有经过同行评审。数据集和代码虽已公开,采用 CC BY 4.0 许可,但论文也承认部分评测配置尚未完整披露。作者提供的一条佐证是,把输入改写并替换标识符后,精确的 CVE 回忆率从 10 掉到 1,而 Pass@1 只移动两三个百分点,说明成绩不完全来自背题。这些边界都要记住。另外 C/C++ 占了 53%,跨语言的结论也要谨慎。

所以卡住 agent 的,不是工具用不熟,而是它没能把缺陷语义翻译成分析器可执行的逻辑。要造出可靠、可复用的检查器,语义理解、反馈驱动的迭代和误报控制三样都得有,换一个更强的模型并不自动解决问题。

它留给下一阶段的问题也很具体。怎么让 agent 在追求"能编译"之外真正盯住缺陷语义,怎么把误报控制从碰运气变成一种稳定的、可训练的能力,以及当任务从占了一半以上的 C/C++ 扩到更多语言时,同一套方法还能不能成立。这些问题眼下没有标准答案。CheckerBench 的价值恰恰在于,它把它们从模糊的直觉变成了一组可以被反复测量的数字,把这道题清清楚楚地摆在了台面上。

想深入可以读原文:CheckerBench: Can Long-Horizon Agents Synthesize Static-Analysis Checkers? arXiv:2610.07557

参考文献

  • CheckerBench: Can Long-Horizon Agents Synthesize Static-Analysis Checkers? arXiv:2610.07557
  • KNighter: Transforming Static Analysis with LLM-Synthesized Checkers arXiv:2503.09002
  • SWE-bench: Can Language Models Resolve Real-World GitHub Issues? arXiv:2310.06770
  • SWE-agent: Agent-Computer Interfaces Enable Automated Software Engineering arXiv:2405.15793
  • OpenHands: An Open Platform for AI Software Developers as Generalist Agents arXiv:2407.16741
  • SWE-Bench Pro: Can AI Agents Solve Long-Horizon Software Engineering Tasks? arXiv:2509.16941
  • Long-Horizon-Terminal-Bench: Testing the Limits of Agents on Long-Horizon Terminal Tasks with Dense Reward-Based Grading arXiv:2607.08964
  • RoadmapBench: Evaluating Long-Horizon Agentic Software Development Across Version Upgrades arXiv:2605.15846
CheckerBench 基准:会修漏洞的 Agent 写不出可复用检查器