news 2026/9/28 7:08:25

Defending Code Reference Harness 补丁生成与验证:`patch` 阶段的可执行验证阶梯实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Defending Code Reference Harness 补丁生成与验证:`patch` 阶段的可执行验证阶梯实战指南
  • 人工智能
  • AI Agent
  • 应用安全
  • 漏洞扫描

【免费下载链接】defending-code-reference-harness

Skills for threat modeling, scanning, triage, patching, plus an autonomous scanning harness you can /customize

项目地址:https://gitcode.com/gh_mirrors/de/defending-code-reference-harness
点击查看免费下载

本文基于仓库文档 docs/patching.md 与配套源码编写,聚焦开源参考流水线 Defending Code Reference Harness 中「补丁生成与验证」这一环节:如何把一个vuln-pipeline run产出的已验证崩溃,变成一个能通过可执行验证阶梯(Verification Ladder)的候选修复,并在人工审查后合入上游。

导读

patch是 Defending Code Reference Harness 自主漏洞发现流水线(recon → find → triage → report → patch)的最后一段:它把 docs/triage.md 之后获得的已验证、已排序的崩溃队列,逐个转化为可供你审查并提交上游的候选修复。本文会带你走完从运行bin/vp-sandboxed patch命令、理解「双容器信任边界」下的补丁循环,到掌握 T0–T3 可执行验证阶梯的每一级判据、再到人工审查补丁和 /patch skill 静态模式的完整流程。读完你将能独立为流水线产出的崩溃生成、验证、审查并准备上游提交的补丁,也能为不适合「文件输入」驱动的目标编写自定义 re-attack 驱动脚本。

⚠️ 前置安全提醒:补丁评估器会执行目标代码并把模型生成的 diff 应用到目标代码上。请与流水线其他阶段保持同等隔离级别,详见 docs/security.md。

patch 阶段在流水线中的位置

patch 阶段是 triage 之后的自然下一步。此时你手上已经有一条经过验证(grade 通过)、去重(dedup)、并附带可利用性报告(report)的崩溃队列,patch阶段的任务是把队列里的每个崩溃变成一份候选修复:

  • 输入:vuln-pipeline run产出的results/<target>/<ts>/结果目录(其中包含崩溃的 PoC 字节、复现命令与 ASAN 轨迹);
  • 输出:落在<results_dir>/reports/bug_NN/下的patch.diff与patch_result.json,与既有的可利用性报告(report.json)并排存放;
  • 过程:每轮迭代的对话记录分别流式写入patch_transcript_itN.jsonl与reattack_transcript_itN.jsonl(N 为迭代序号)。

值得注意的是,/patchskill 有两种模式(见下),文档对 CLI 模式着墨最多,但「审查生成的补丁」一节对静态模式同样适用。

快速开始

patch 阶段随流水线一同交付,无需额外安装。唯一的前置条件在目标(target)的config.yaml中:

  • build_command:必需,用于在容器内重新构建打过补丁的源码树(验证阶梯 T0 依赖它);
  • test_command:可选,用于回归验证(T2);未配置时 T2 被跳过。

仓库自带的四个目标(targets/alsa/、targets/canary/、targets/drlibs/、targets/htslib/)均已配置build_command,其中部分目标额外配置了test_command。例如 targets/canary/config.yaml 中:

build_command: gcc -O1 -g -fsanitize=address -fno-omit-frame-pointer -o /work/entry /work/entry.c test_command: >- printf 'A\003abc' > /tmp/t_a && printf 'Bhello' > /tmp/t_b && printf 'C\001\002' > /tmp/t_c && /work/entry /tmp/t_a && /work/entry /tmp/t_b && /work/entry /tmp/t_c

而 targets/drlibs/config.yaml 只配置了build_command(gcc -O1 -g -fsanitize=address -fno-omit-frame-pointer -o /work/entry /work/entry.c -lm),没有test_command——此时验证阶梯会跳过 T2。

两种运行方式:

# 方式一:流水线运行产出结果之后 bin/vp-sandboxed patch results/<target>/<ts>/ --model <model> # 方式二:在预烘焙的 canary fixture 上独立试跑(无需先跑流水线) bin/vp-sandboxed patch targets/canary/fixtures/results_sample --model <model>

第二种方式非常适合第一次上手:仓库在 targets/canary/fixtures/results_sample/ 里预置了三份result.json(run_000/run_001/run_002),可以直接作为 patch 阶段的输入,无需先完成一次完整的 find+grade 循环。

补丁循环(patch loop)如何工作:双容器信任边界

补丁循环在 harness/patch.py 中实现,其核心设计是双容器信任边界:

  1. patch agent 运行在容器 A(沙箱化,见 docs/agent-sandbox.md)。容器内提供源码、PoC、复现命令与 ASAN 轨迹。它的提示词(见 harness/prompts/patch_prompt.py 的FULL_TASK)要求它:

    • 修复根因而非崩溃点——先从崩溃点反向追踪坏值的来源("Trace backward from the crash site to where the bad value originated. The fix usually belongs there, not at thememcpy/deref that ASAN flagged.");
    • 排查同类调用点——grep 具有相同模式的兄弟调用点,diff 要么覆盖全部,要么在 rationale 中说明原因;
    • 保持 diff 最小化——"Smallest change that fixes the root cause. No refactoring, no drive-by cleanup, no reformatting.";
    • 对抗性自检——以攻击者视角重新读自己的 diff,若能命名一个绕过该检查到达同一坏状态的输入变体,说明修复在错误层级,应回到第 2 步。
  2. grader agent 运行在容器 B,从同一镜像全新创建。从容器 A 穿越到容器 B 的唯一东西是 diff 字节本身——grader 永远看不到 patch agent 的推理过程,因此不可能被"说服"批准一个糟糕的修复(harness/patch_grade.py 的模块注释明确写了这一设计意图)。grader 应用 diff 后攀爬验证阶梯(详见下一节),任何一级失败立即停止。

  3. 失败证据回填。若某一级失败,失败证据(如编译错误、ASAN 轨迹、回归测试输出)会进入下一轮尝试的提示词(patch_prompt.py中的RETRY_SECTION:"Your last diff was graded and failed at tier{failed_tier}"),补丁循环重新运行,最多--max-iterations次。测试 tests/test_patch.py 的test_run_patch_retries_on_failed_grade验证了这一点:第一次 grade 返回 t1 失败后,第二次调用的 prompt 中必须包含"Previous attempt failed"与"still crashes"证据。

从源码结构看,循环的判定逻辑(harness/patch.py 的_failed_tier)按固定优先级报告失败层级:t0(build)→ t1(PoC 仍崩溃)→ t2(测试回归)→ re-attack(发现变体)。此外,patch agent 必须通过<patch_path>标签声明其生成的 diff 路径;若未输出该标签或标签指向的文件为空,也会作为失败证据要求重试(对应测试test_run_patch_no_tag_retries)。

另一个值得注意的工程细节:run_patch会确保源码根目录是一个 git 仓库并创建基线提交,从而让 agent 的git diff输出确定可复现;同时把构建出的二进制加入.gitignore(patch.py中的注释解释了原因——否则重建的二进制会进入 diff,导致 grader 侧git apply拒绝)。

验证阶梯(The Verification Ladder)

在补丁循环中,grader agent 按顺序执行四个检查,遇到第一个失败即停止。每一级都是一个可执行判定器(executable oracle)——运行某段程序,根据结果做通过/失败判定,没有任何一级基于模型的主观判断。另外还有第五个可选步骤(--style启用),用模型审查补丁风格,但仅作咨询,永不把关。

层级要回答的问题判定器(Oracle)patch_result.json中的字段
Build(T0)打过补丁的源码树能编译吗?git apply+build_command退出码t0_builds
Reproduce(T1)原始崩溃消失了吗?退出码为 0且输出中无AddressSanitizer:t1_poc_stops
Regress(T2)是否破坏了既有行为?test_command退出码(未配置则跳过)t2_tests_pass
Re-attack根因被修复了,还是只修了这条输入?全新的 50 轮 find agent 攻击打过补丁的二进制;由 ASAN 裁决re_attack_clean
Style(T3)维护者会接受它吗?LLM 打分 0-10;仅咨询,永不把关t3_style_score

一个补丁在 build、reproduce、regress(或无测试套件)、re-attack全部干净时才算通过。源码中这一判定浓缩在 harness/artifacts.py 的PatchVerdict.passed属性:

@property def passed(self) -> bool: return ( self.t0_builds and self.t1_poc_stops and self.t2_tests_pass is not False # None = 无测试套件,不算失败 and self.re_attack_clean is not False # None = 未运行 re-attack(--no-reattack) )

各层级的具体实现位于 harness/patch_grade.py:

  • T0:将 diff 写入容器,依次git apply --check与git apply(多 diff 时先 check 跳过无法干净应用的),然后执行target.build_command,受build_timeout_s(config.yaml 中可配,默认 1800 秒)约束;退出码非 0 即失败并截取证据(EVIDENCE_LIMIT = 4000字符裁剪)。
  • T1:把 PoC 字节写入容器,执行适配后的复现命令(PoC 路径替换为/tmp/poc.bin),判定条件_t1_passes为rc == 0 and "AddressSanitizer:" not in (stdout + stderr);600 秒超时视为失败,错误信息甚至提示"hang — likely a botched loop bound"。
  • T2:仅当target.test_command存在时执行,1200 秒超时,退出码为 0 才通过。
  • Re-attack:将打过补丁的容器docker commit成临时镜像,用run_find启动一个50 轮(REATTACK_MAX_TURNS)的全新 find agent(accept_dos=False)攻击该二进制,ASAN 决定是否崩溃;崩溃即失败,并把崩溃轨迹作为证据。临时镜像在finally块中清理(docker_ops.rmi)。
  • T3:单次无工具的 LLM 调用(max_turns=5),对 diff 打 0-10 风格分(STYLE_JUDGE_TEMPLATE:0-3 为错误层级/压制症状,4-6 为正确但嘈杂,7-10 为最小化、精准、匹配风格),分数解析失败或越界则记None,永不参与通过判定。

T0–T2 阶段只运行目标代码(docker_ops.exec_sh),从不发起claude -p调用,因此 grader 容器被配置为auth=None(不把 API 凭据放进一个运行着"被 PoC 专门设计去崩溃的二进制"的容器环境)且network="none"(无外部调用需求,也就不需要 egress;注释特别指出若不加这个限制,沙箱默认会给 bridge 网络即完整出网,这对一个喂入攻击者控制输入的进程是危险的)。只有 re-attack 与 style judge 阶段才自行派生带agent_env与网络的容器。

为什么需要 re-attack?

一个能编译、能挡住特定 PoC 的补丁通常很容易写出来。已发表的模型生成安全补丁评测显示:约60%能通过 build+reproduce 检查,但不足 15%能撑过模糊测试与差分测试。占主导的失败模式是:在崩溃点加一个边界检查,却让坏值仍能从略有不同的输入到达。re-attack 正是针对这一失败模式设置的防线——一个 50 轮的 find agent 会试图构造绕过该补丁的变体输入。

文档同时给出严谨的边界说明:re-attack 通过只是有用的信号,不等于"根因已被证明修复"。它在 50 轮内可构造绕过输入时判别力很好,但可能漏掉"错误层级修复"(wrong-layer fix)——这类修复的绕过输入往往更难构造。

审查生成的补丁(Reviewing Generated Patches)

验证阶梯证明了"特定崩溃被修复",但不能证明根因被解决,也不能证明 diff 没有引入新问题。阶梯不会对 diff 做语义审查——不检查是否引入了新漏洞、是否在修复之外破坏了逻辑,或其他任何问题。

因此,请把patch.diff当作一份需要人工阅读的强草稿,在它去向任何地方之前先审查。文档列出的常见问题包括:

  • 范围蔓延(Scope creep):修改了与崩溃路径无关的文件或函数;
  • 压制而非修复(Suppression instead of fix):try/except: pass、对精确 PoC 值提前 return、禁用触发崩溃的断言;
  • 新攻击面(New attack surface):新增解析逻辑、信任来自输入的新长度字段、为了让修复"生效"而削弱其他地方的校验;
  • 诊断正确、修复错误(Correct diagnosis, wrong fix):正确识别了需要修改的模块,但补丁过窄,破坏了其他东西。

在上游提交前,有两个相对容易的额外步骤值得做:

  1. 对抗性验证(Adversarial validation):在一个全新会话中对最终patch.diff重跑一次——"说出一个输入变体,它到达同一坏状态却不会触发补丁的检查"。patch agent 在生成时已经问过自己类似的问题,但在全新上下文中对着最终 diff 问,更有可能找到修复的缺口。
  2. 请求简化(Ask to simplify):同样在全新会话中,只要求"简化为能修复根因的最小改动"。patch agent 被提示词要求产出最小 diff,但它对"最小"的认知锚定在刚推理过的 finding 上;全新上下文的简化通常能可靠地裁剪 diff。

提示注入威胁模型

patch agent 的提示词会读取目标衍生的数据(ASAN 轨迹、可利用性报告,以及重试时的 build/test 输出)。流水线用每次调用随机分隔符(make_nonce(),见 harness/prompts/untrusted.py)将这些数据围栏起来,并指示 agent 把它们当作数据而非指令处理(patch_prompt.py中的<untrusted_data id="{nonce}">块与 untrusted-data 说明)。但文档明确提醒:提示词层级的围栏是缓解措施,不是保证。如果你针对的是不完全可信的第三方代码,被投毒的目标影响可能渗透到 diff 生成与审查中。更完整的威胁模型见 docs/security.md。

CLI 参考

bin/vp-sandboxed patch <results_dir> --model <m> # patch 所有唯一 bug bin/vp-sandboxed patch <results_dir> --bug N # 只 patch bug_NN bin/vp-sandboxed patch <results_dir> --parallel # 并发运行 patch agent bin/vp-sandboxed patch <results_dir> --no-reattack # 跳过阶梯中的 re-attack 步骤(更快,但更弱) bin/vp-sandboxed patch <results_dir> --style # 运行可选的、仅咨询的风格评审 bin/vp-sandboxed patch <results_dir> --max-iterations N # 最大补丁循环次数(默认 5) bin/vp-sandboxed patch <results_dir> --max-turns N # 每轮迭代的 agent 预算(默认 200)

以上参数与 harness/cli.py 中patch子命令的 argparse 定义一一对应(常量PATCH_MAX_TURNS = 200、DEFAULT_MAX_ITERATIONS = 5定义于 harness/patch.py)。_cmd_patch的实现要点:

  • 先对results_dir做 dedup 分组,并按"有通过验证的组优先、再按签名排序"排出与 report 阶段一致的bug_NN序号(确保这里的bug_NN与reports/bug_NN/对应);
  • --bug N只处理序号为 N 的组;
  • 目标必须配置了build_command,否则直接报错退出("the patch grader needs an in-container rebuild step");
  • 若目标镜像不存在则先构建;
  • 每个 bug 会读取同目录下已有的report.json文本,作为可利用性报告上下文注入 patch agent 的提示词(harness/prompts/patch_prompt.py 的report_section,截断至 4000 字符);
  • 运行结束后按patch_verified/patch_rejected/no_diff汇总,任一非patch_verified都会使命令退出码为 2。

--model是必填的(或通过VULN_PIPELINE_MODEL环境变量提供);认证由 harness/auth.py 解析(Bedrock / Vertex /ANTHROPIC_API_KEY/CLAUDE_CODE_OAUTH_TOKEN四选一,详见 docs/agent-sandbox.md)。patch 属于会派生自主 agent 的子命令,因此在非显式覆盖(--dangerously-no-sandbox)时会拒绝在 gVisor 沙箱之外启动。

Harness-driven re-attack:自定义目标驱动

re-attack 默认通过"以输入文件驱动二进制"(./binary <input>)来攻击目标。对于无法这样驱动的目标——需要启动器、环境设置、多进程编排或非文件输入通道——你可以自己提供驱动脚本:

  1. 写一个知道如何运行你的目标的脚本;
  2. 把它打进目标的 Docker 镜像;
  3. 在目标config.yaml中用reattack_harness:指向它。

re-attack 期间,find agent 把候选 PoC 写成/poc/下的文件并运行你的脚本;补丁循环的其余部分不变。该字段由 harness/config.py 的TargetConfig.reattack_harness承载(config.yaml可选键),并被 harness/find.py 与 harness/prompts/find_prompt.py 接入——后者在reproduction_command标签中直接使用该脚本路径,并要求"3 次中复现 3 次"才算有效崩溃。测试 tests/test_focus.py 的test_reattack_harness_switches_template验证了该字段会切换提示词模板。

你的脚本是唯一需要理解目标的东西,agent 与流水线只依赖脚本的退出码。契约如下:

  • 对/poc/下的每个文件,用插桩后的目标运行一次(每个 PoC 使用全新状态),并捕获各自的 sanitizer 输出;
  • 若有任何 PoC 使目标崩溃,退出码1并打印 sanitizer 轨迹;
  • 若所有 PoC 均未崩溃,退出码0;
  • 若目标完全无法启动,退出码2。

Campaign-style patching:/patchskill 静态模式

bin/vp-sandboxed patch依赖bin/vp-sandboxed run的输出。如果 finding 来自别处(独立扫描器、人工审查、纯文字报告),或者你要修的是跨越多个调用点的整类 bug而非单个崩溃,CLI 模式就不适用了。此时用/patchskill 的静态模式:

# 为 TRIAGE.json 中严重度最高的 5 个已确认 finding 起草修复 > /patch ./TRIAGE.json --repo ./my-service --top 5

静态模式接受静态 finding(TRIAGE.json或VULN-FINDINGS.json)。对每个 finding,skill 运行两个 agent:

  • patch agent阅读相关代码并写出候选修复(diff);
  • reviewer agent在干净上下文中评判该 diff——评估范围、有效性以及引入的新攻击面(不执行任何代码)。

不会直接应用任何东西到你的仓库。生成的 diff 落在./PATCHES/供你审查,每个结果都标注为verified: static_review_only,以区别于走完验证阶梯的补丁。静态模式不涉及运行 PoC 或修改目标代码,因此它属于只读/只写操作(详见 README.md 的安全说明),无需沙箱即可交互式运行。

静态模式没有 PoC 可以重跑,所以回归测试是唯一真正可执行的检查。文档给出的操作法:先让 patch agent在写补丁之前写一个能复现该 bug 的测试——在当前代码上运行并确认它失败,然后应用修复并确认它通过。没有"先失败后通过"的测试,你就无法证明这个 bug 曾经真实存在,修复也可能在日后悄然回归。

当修复本质上是迁移(When the fix is really a migration)

有时 finding 不是一个 bug,而是一个模式——同一处不安全调用散布在几十个调用点,一个 PR 装不下。此时可按如下工作流运行:

  1. 研究(Research):一个 agent 阅读 finding 与代码库,写出迁移计划——哪个模式不安全、安全替代是什么、调用点都在哪里;
  2. 把计划变成测试(Turn the plan into tests):为每个调用点写一个"现在失败、迁移后通过"的测试。这套测试扮演流水线中 ASAN 的角色,即判定"何时算做完"的检查;
  3. 拆分成工单(Split into tickets):把测试分成可独立合并、小到足以审查的块;
  4. 并行补丁(Patch in parallel):每个工单一个 worker 子 agent,各在独立 git worktree 中,循环修改其分块,直到"它的测试"与"项目既有测试"全部通过;
  5. PR 前把关(Gate before the PR):每个 worker 的 diff 必须通过测试、一次独立 bug 扫描、以及一个不授予任何工具的 agent 的代码审查,外加项目本身要求的检查。PR 本身由人类审查。

⚠️ 与 CLI 模式相同,静态模式下 patch agent 同样读取目标衍生的数据(ASAN 轨迹、可利用性报告等),围栏与注入威胁模型同上一节所述,处理第三方不完全可信代码时需保持同等警惕。

小结与边界

patch阶段把"已验证的崩溃"升级为"通过可执行验证阶梯的候选修复":T0 编译、T1 原 PoC 不再崩溃、T2 测试套件不回归、re-attack 用全新 find agent 抵御绕过变体,全程以编译退出码、ASAN 输出与测试退出码这些可执行判定器把关,模型判断只出现在可选的风格咨询(T3)中。但文档同时反复强调其边界:验证阶梯证明的是"特定崩溃被修复",不是"根因被解决"或"diff 无新问题";re-attack 通过只是有用信号。上游合入之前,人工审查(识别范围蔓延、压制式修复、新攻击面、错误层级修复)+ 全新会话的对抗性验证与简化,仍是不可省略的步骤。对无法用文件输入驱动的目标,reattack_harness提供了一条把"如何运行目标"委托给自定义脚本的通道;对静态 finding 与整类 bug 迁移,/patchskill 的静态模式则提供了一条不经运行验证、以static_review_only标注的轻量路径。

  • 人工智能
  • AI Agent
  • 应用安全
  • 漏洞扫描

【免费下载链接】defending-code-reference-harness

Skills for threat modeling, scanning, triage, patching, plus an autonomous scanning harness you can /customize

项目地址:https://gitcode.com/gh_mirrors/de/defending-code-reference-harness
点击查看免费下载

相关推荐

上一篇:gitignore.io服务网格设计:微服务通信
下一篇:如何在5分钟内配置Dracula for JetBrains:从安装到美化的完整教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 7:08:17

基于Dify工作流构建AI复盘应用:hindsight项目实战拆解

1. 项目概述&#xff1a;hindsight 到底要解决什么问题这个项目的名字挺有讲究。hindsight 这个词&#xff0c;英文直译是"后见之明"——事后看一件事&#xff0c;往往比当时当事看得更清楚、更冷静、更全面。我在做这个项目的时候&#xff0c;一直在想一个问题&…

作者头像 李华
网站建设 2026/9/28 7:08:00

SWD协议详解:从寄存器访问时序到调试器连接故障排查

开始调试一个全新的 ARM 板子&#xff0c;或者是正在调试的板子突然连不上调试器&#xff0c;大多数人的第一反应是先检查接线&#xff0c;然后怀疑目标板供电&#xff0c;实在不行就把目标板电断了重来。但如果你问过自己&#xff1a;SWD 协议究竟在线上是怎么跑的&#xff1f…

作者头像 李华
网站建设 2026/9/28 7:08:00

网心云OES Plus刷Armbian:短接TP1/TP2解锁RK3328

1. 项目概述&#xff1a;为什么有人愿意花三小时给一台网心云盒子刷Armbian&#xff1f;“网心云OES Plus刷Armbian”——这行字在极客论坛、NAS交流群和二手硬件交易帖里反复出现&#xff0c;背后不是简单的“换个系统”&#xff0c;而是一场对设备底层控制权的争夺。我第一次…

作者头像 李华