如何构建模型无关的漏洞发现 Harness

Cloudflare AI Bl15 小时前
分享到

背景:不要把安全扫描押注在单一模型上

面向企业级代码库做漏洞发现时,单个模型、单条提示词或一次性 Agent 会话很难承担完整任务。原因并不复杂:

  • Agent 往往一次只能维持一个主要假设;
  • 真实代码库很快会填满上下文窗口;
  • 上下文压缩可能丢失前面发现的线索;
  • 单仓库分析无法覆盖跨仓库依赖关系;
  • 如果模型供应商调整能力、缓存、温度或推理预算,依赖单一模型的系统会变得脆弱。

更稳妥的做法,是把模型视为可替换组件,把长期有效的部分放在编排层:任务状态、阶段流转、去重、验证、恢复、跨仓库追踪和人工可读报告。

两个常见疑问

为什么不用 subagent?

Subagent 很有用,也适合作为起点。但安全分析通常需要数百个彼此独立的调查任务,它们要能跨运行保留状态,不共享同一个上下文窗口,并且后续可以重新定界、交叉引用和去重。

这类需求包括:

  • 持久化;
  • 去重;
  • 可恢复执行;
  • 跨仓库依赖追踪;
  • 多阶段验证;
  • 可审计的任务队列。

这些更像是编排系统问题,而不是单靠提示词就能解决的问题。

这是不是只适用于前沿模型?

核心并不是某个具体模型,而是 Harness。漏洞发现可以使用当前最适合任务的模型,但系统设计应从第一天开始保持模型无关。

一个重要经验是:不同模型看同一目标时,发现的漏洞集合并不完全相同。因此,可以用一个模型负责初步发现,用另一个模型负责验证,让不同逻辑路径彼此交叉检查。

从一个安全审计 Skill 开始

实践起点是一个约 450 行的 security-audit skill,用于在单个代码仓库上运行。通过反复调整提示词,它能发现真实漏洞。后续的 Harness 只是把其中各阶段工程化、持久化和可扩展化。

这个 skill 最初设计为一次会话内完成 7 个阶段:

  1. 三个并行研究 Agent 做侦察,并写出 architecture.md
  2. 每类攻击由一个 Hunter Agent 执行,目标不是普通代码审查,而是尝试“打破”代码;
  3. 对每个发现进行对抗式验证,尝试推翻它;
  4. 对幸存发现生成面向人的漏洞报告;
  5. 按 schema 输出 findings.json,并进行机械校验;
  6. 用一个全新 Agent 独立复核每个发现;
  7. 将通过复核的发现提交到接收接口。

这个 skill 与后来的 Harness 阶段几乎一一对应:

Skill 阶段 Harness 阶段
Recon Agent 写 architecture.md Recon
Hunter 按攻击类别运行 Hunt
Validator 尝试推翻发现 Validate
幸存发现形成报告 Report
findings.json 做 schema 校验 机械校验行号、函数和路径
全新 Agent 再次复核 独立验证

单次运行的局限

单次运行可以发现一部分问题,但覆盖并不稳定。实践中观察到,单次运行只能发现多次运行累计结果中的一部分,且更容易命中相对简单、不那么隐蔽的问题。

当流程变成“运行十次,然后人工 diff”时,就说明需要更正式的 Harness 了。

最早遇到的三个主要瓶颈是:

1. 上下文耗尽

运行一段时间后,上下文窗口会被填满。模型可能在压缩上下文时丢掉早先追踪的漏洞线索。解决方式是把状态完全外置,把 LLM 当作无状态计算引擎使用。

2. 缺少持久化

如果中途崩溃、触发速率限制或连接不稳定,任务可能需要从头开始。对长时间安全分析来说,这种损耗很昂贵。

3. 难以进行跨仓库推理

单仓库会话看不到应用之间的调用关系和依赖边界,而不少漏洞恰恰出现在组件接口之间。

最小可行 Harness 应该长什么样?

一个真正可用、但仍然足够简洁的 Harness,可以先只包含:

  • Recon:理解目标架构;
  • Hunt:按攻击类别寻找问题;
  • Validate:验证和尝试推翻发现;
  • 数据库:保存阶段状态、发现、任务和运行记录;
  • 独立 Validator:只能验证,不能提交自己的新发现。

不建议一开始就追求完整系统:

  • 如果只有一个重要仓库,暂时不需要跨仓库追踪;
  • 如果噪声还不多,暂时不需要专门的 Dedup Agent;
  • 先在开发环境中把 skill 和提示词调好;
  • 只有当缺少某个架构阶段真正拖慢流程时,再把它补上。

将 Skill 固化为流水线

把一次性 skill 变成覆盖整个代码库舰队的扫描系统,关键在于把每个阶段拆成独立 Agent,并在后面接数据库、前面接编排器。

实践中,这套系统覆盖了 128 个不同代码仓库,并能自动发现和追踪相关依赖。代码库语言包括 Rust、Go、C、Lua、TypeScript、Python,以及多种配置管理系统和静态配置。

该 Harness 没有为每种语言做专门调优,而是让模型处理语法差异,编排层关注更高层的安全流程:任务分解、依赖追踪、验证和去重。

两阶段漏洞研究流程

整体流程可以拆成两个主要系统:

  1. Vulnerability Discovery Harness(VDH):漏洞发现引擎,主动扫描代码库并产出潜在安全问题;
  2. Vulnerability Validation System(VVS):漏洞验证系统,接收一个或多个 Harness 的输出,再执行去重、判断和修复流转。

一个关键设计是:VDH 和 VVS 使用不同模型。

这样做有两个好处:

  • 安全上:模型 B 会审查模型 A 的输出,相当于用不同训练分布和推理权重进行对抗式复核;
  • 运营上:模型供应商可能改变行为细节,系统不应依赖某个模型长期保持完全一致。

阶段一:漏洞发现 Harness(VDH)

VDH 不只是让模型“看代码”,而是由多个阶段组成。

阶段 主要职责 关键机制
Recon 绘制目标架构,识别潜在威胁向量 3 个并行 Recon 子 Agent 写 architecture.md
Hunt 按攻击类别执行探索、编译片段、探测二进制 可派生兄弟任务,并向 Wishlist 工具读写需求
Validate 对发现做机械校验和对抗式验证 先由普通代码检查 schema、路径等,再由隔离 Agent 尝试推翻发现
Gapfill 补齐覆盖不足的区域 对薄弱的“区域 × 攻击类别”组合生成新 Hunt 任务
Dedup 合并重叠发现 结合确定性代码和 Agent,按根因聚类并实时折叠
Trace 追踪跨仓库依赖 识别漏洞是否通过依赖链影响其他仓库

其中最重要的不是某个 Agent,而是阶段之间的“胶水”:

  • 每个任务都能恢复;
  • 每个发现都能追踪状态;
  • 每个阶段都能被替换;
  • 每个验证步骤都尽量隔离;
  • 机械检查和语义判断分离。

降低误报:先机械检查,再对抗式推翻

漏洞发现系统如果没有强验证环节,很容易被误报淹没。

有效做法是把验证拆成两层:

  1. 机械校验:检查 schema、路径、行号、函数名、文件是否存在等;
  2. 语义验证:由隔离 Agent 读取相关代码,主动尝试证明该漏洞不存在。

只有经得起反驳的发现,才进入后续流程。

这种“先质疑,再提交”的流程能显著减少模型幻觉带来的噪声,也能避免 Hunter Agent 同时扮演发现者和裁判。

外置状态:把 LLM 当作无状态计算单元

长任务中,最脆弱的部分是上下文窗口。Harness 的核心策略是把状态放到系统外部,而不是让模型记住一切。

外置状态至少包括:

  • 仓库和目标范围;
  • 架构摘要;
  • 攻击类别;
  • 已覆盖区域;
  • 候选发现;
  • 验证结果;
  • 去重关系;
  • 跨仓库依赖;
  • 任务失败与重试记录。

这样即使某次模型调用失败,也可以从数据库中的任务状态继续运行,而不是丢掉整轮分析。

什么时候需要跨仓库追踪?

跨仓库追踪的价值在于识别组件边界上的漏洞。例如一个库本身看似安全,但调用方以危险方式使用它;或者一个仓库暴露的接口,在另一个仓库中形成实际攻击路径。

但这不是最早应该做的功能。只有当组织中存在多个彼此依赖、且安全影响显著的仓库时,跨仓库追踪才值得优先投入。

可复用的设计原则

这套方法背后的原则可以概括为:

  1. 模型无关:不要绑定单一模型或供应商;
  2. 阶段化:把侦察、发现、验证、报告、去重拆开;
  3. 状态外置:避免把关键记忆放在上下文窗口里;
  4. 对抗式验证:每个发现都要经过反驳;
  5. 机械检查与智能判断分离:能用代码检查的,不交给模型;
  6. 先小后大:从单仓库 skill 开始,逐步演进成 Harness;
  7. 不同模型交叉复核:发现和验证尽量不要依赖同一套模型判断。

结论

构建漏洞发现 Harness 的重点,不是找到一条完美提示词,也不是押注某个最强模型,而是把安全研究流程工程化。

从一个可运行的 security-audit skill 开始,先验证提示词和攻击场景是否有效;当上下文、持久化、重复运行和跨仓库分析开始成为瓶颈,再逐步加入数据库、编排器、独立验证、去重和依赖追踪。

最终可持续的部分不是某个模型,而是这套可恢复、可替换、可审计的安全分析流水线。

评论

请登录后发表观点

暂无数据