让两位经验丰富的评审读同一个 pull request,你会拿到两份不同的清单。一位注意到 URL 里的标识符从未与会话核对;另一位注意到重试循环每次失败都留下一个未关闭的数据库连接。两人都没错,两人也都不完整。语言模型的表现同样如此,原因也相同:评审注意到什么,取决于它此前见过什么。
一致本身就是信号
单个模型会告诉你它有多确信。这个数字有价值,但它是自述的:模型在给自己的作业打分。当两个由不同实验室、用不同数据、带着不同弱点训练出来的模型,各自独立地指向同一个文件里的同一个弱点时,这种一致是来自模型之外的证据。这是自动化审查最接近第二诊疗意见的时刻。
反过来同样重要。只有一个引擎报告的问题并不因此就是错的,丢掉它等于丢掉单个评审最擅长发现的那类缺陷。它应当被报告并标明来源,由你自己来权衡。
一个引擎沉默的地方
实践中,两个引擎分歧最大的是那些需要推理而非模式匹配的缺陷类型:
- 逻辑缺陷:被重复计算两次的折扣;退款之后还能再接受一次退款的状态机;作用在错误对象上的归属校验。
- 框架特有的授权问题:中间件保护了一组路由,却没有保护挂在旁边的 API 处理器。
- 资源与内存泄漏:每个请求都会打开、却只在正常路径上释放的监听器、定时器和连接。
- 藏在注释或测试数据里的提示注入——如果不是专门去找,它读起来就是无害的文字。
两份报告如何合成一份
合并环节决定了第二个引擎是物有所值,还是把噪声翻倍。我们确定的规则是:
- 问题按标识缺陷的要素匹配,而不是按措辞:它的 CWE 分类,以及它所在的文件。
- 匹配上的问题只报告一次,取两者中更严格的严重级别,因为值得据以行动的是更安全的那种解读。
- 保留开发者能直接使用的写法:更完整的证据、具体的补丁、有序的修复步骤。
- 得到相互印证的问题会提升为高置信度,并同时标注两个引擎。
- 只有一个引擎看到的问题同样保留,并注明是哪个引擎发现的。
两个引擎同时读取同一份源码,因此审计不会耗时翻倍;翻倍的是运行成本,这也是它属于 Pro 套餐的原因。如果第二个引擎不可用或返回了无法使用的结果,审计不会失败,而是由第一个引擎单独完成。
这一切都不能让报告变成真理。两个引擎可能一致,也可能同时出错;每条问题下方引用的证据,正是为了让你去核对而不是去相信。相互印证带给你的是一个顺序:当一百条问题摆在面前而你只有一个下午时,先从两个独立评审都看到的那些开始。