关于 post-patch-validation
post-patch-validation 用于验证安全补丁是否解决报告的问题,并检查周边行为是否受影响。它指导审查者针对固定版本运行可复现的本地检查,并汇总证据与覆盖缺口。
主要功能
- 01针对固定版本规划检查
- 02测试漏洞利用与根因变体
- 03检查行为、回归与安全性
- 04记录日志和验证证据
- 05报告发现与覆盖缺口
适用场景
- 01验证 AI 生成的安全补丁
- 02检查补丁可能遗漏的漏洞变体
- 03发现行为变更带来的回归
- 04为补丁修订提供可复现的失败案例
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ trailofbits/ skills --skill post-patch-validation安装后在 Claude Code 中输入/post-patch-validation确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamlreferences/evidence-model.mdscripts/post_patch_validation.pypyproject.toml详细介绍
补丁后验证
针对已报告的 Bug 及其影响的周边代码,验证安全补丁。为补丁作者提供可复现的失败案例以供修复,并指出仍需测试的内容。对人工编写的补丁和 Agent 编写的补丁执行相同检查。仅凭差异、作者、上游实现和原始概念验证,无法确定补丁正确。
适用场景
- 已有安全修复、补救提交、补丁文件或拉取请求。
- AI 生成的补丁需要在人工审查或合并前进行验证。
- 修复可能覆盖了一条利用路径,却遗漏了同一根因的变体。
- 安全修复可能改变正常行为或引入新的漏洞。
- 补丁作者需要在进行下一轮修改前,获得具体失败案例和覆盖缺口。
不适用场景
- 尚无补丁;请先发现漏洞或实现修复。
- 任务是根据报告审查审计发现,但不执行补丁证据。
- 任务仅是将某项发现转化为永久的项目测试。
- 目标是远程或生产环境。本技能仅执行本地代码和测试。
- 用户未授权执行仓库代码或测试套件。
快速开始
-
固定易受攻击的基线和已打补丁的输入。优先使用不可变提交。对于未提交的工作,请先创建二进制补丁文件;不要在用户的工作树中验证。
-
生成固定版本的计划:
uv run {baseDir}/scripts/post_patch_validation.py scaffold \ --repo . \ --base-ref <vulnerable-ref> \ --patched-ref <patched-ref> \ --finding-id <stable-id> \ --finding-summary "<root cause and impact>" \ --evidence-level runtime \ --output post-patch-validation/plan.json对于补丁文件,请使用
--patch-file <path>,而不是--patched-ref。选择最高且诚实的证据级别:仅检查源码或补丁不变量时使用source;编译或分析目标代码,但未执行已报告行为时使用build;检查会执行已报告行为及其安全断言时使用runtime。 -
检查发现、差异、调用方、同类路径、清理和错误处理路径,以及现有测试。在生成的计划中填写
checks。运行print-schema查看结构架构:uv run {baseDir}/scripts/post_patch_validation.py print-schema -
在执行代码前,运行
validate-plan完成验证,包括覆盖范围、命令限制和固定的输入:uv run {baseDir}/scripts/post_patch_validation.py validate-plan \ --plan post-patch-validation/plan.json -
执行证据计划:
uv run {baseDir}/scripts/post_patch_validation.py run \ --plan post-patch-validation/plan.json \ --output post-patch-validation/results -
提交
result.json、report.md、证据级别和完整的assessment。将每项发现连同其检查 ID、断言和保存的日志反馈给补丁作者。分别指出每个验证缺口,包括与有证据支持的发现同时存在的缺口。作者修订补丁后,固定新的输入并保存一轮全新的验证。保留之前的证据。即使通过了所有提供的检查,仍须经人工审查后才能接受。
证据约定
运行器会拒绝不完整的计划。每种类型至少提供一项检查:
| 类型 | 必需观察结果 |
|---|---|
control | 无害的测试框架在基准版本和补丁版本上均成功 |
exploit | 原始安全断言在基准版本上失败,在补丁版本上成功 |
variant | 一个不同的根因变体在基准版本上失败,在补丁版本上成功 |
behavior | 未受影响的行为成功,且选定输出的字节完全相同 |
regression | 有针对性的非安全回归检查在两个修订版本上均成功 |
security | 相邻/新漏洞检查在基准版本和补丁版本上均成功 |
suite | 现有项目测试套件、消毒器或确定性模糊测试活动在补丁版本上成功 |
命令必须是 argv 数组,不能是 shell 字符串。将复杂的设置放入已检入的脚本或计划产物中,并使用 {plan_dir} 调用。运行器会固定区域设置/时区/哈希种子输入,按 ID 的字典序执行检查,记录原始 stdout/stderr,且绝不会修改原始工作树。每项检查的 timeout_seconds 默认为 300,接受 1 到 3600 之间的整数。超时会留下验证缺口。仅凭超时无法证明发生了回归。每个计划还必须包含一个排序后的 submodules 数组(没有子模块时为 [])。脚手架会根据变更文件清单推断受影响的 Gitlink。运行器会从源代码库现有的 Git 模块对象初始化这些固定提交,绝不会从 .gitmodules 网络 URL 获取;请在验证前于源代码库中完成初始化或获取。
Exploit 和 variant 检查必须证明其已运行
非零退出状态并不意味着漏洞已复现。导入错误、构建失败、缺少依赖项以及安全断言失败都会以非零状态退出,对运行器而言无法区分。每项 exploit 和 variant 检查都必须在评估断言前立即打印并刷新 PPV_REACHED,两个修订版本上都必须如此:
"argv": ["python3", "-c", "import app; value = app.render('<'); print('PPV_REACHED', flush=True); assert value == '<'"]
该标记也会通过环境变量 PPV_REACHED_MARKER 提供。它必须输出到stdout,且单独占一行。不会扫描 stderr,因为 Python SyntaxError 回溯会回显出错的源代码,否则即使测试框架什么也没执行,也会满足检查条件。未输出该标记的运行会留下 marker_missing 缺口。其他检查得出的独立发现仍会保留在结果中。请显式刷新:如果测试框架的负载发生段错误或调用 _exit,缓冲输出会丢失,该检查也就无法提供自己的证据。
这些检查也会屏蔽版本信息。不会为它们展开 {side},其环境中不会包含 PPV_SIDE,检出目录会随机命名,并且计划验证器会拒绝任何 argv 或 env 中提及两者的 exploit 或 variant 检查。如果断言能获知自己运行在哪个修订版本上,就可以针对该信息而非代码本身进行断言,这是伪造复现后再修复的最简单方式。
环境
检查在固定的最小环境中运行:PATH、HOME、少量临时/用户键,以及
LANG/LC_ALL=C、TZ=UTC、PYTHONHASHSEED=0、NO_COLOR、TERM=dumb。调用方环境中的其他所有内容都会被清除。需要更多环境变量的工具链会显式获取:
uv run {baseDir}/scripts/post_patch_validation.py run \
--plan post-patch-validation/plan.json \
--output post-patch-validation/results \
--allow-env JAVA_HOME --allow-env CARGO_HOME
转发的名称和值会记录在 result.json 中。请求的变量未设置时会报错,而不会使用空字符串。以下两类变量会被直接拒绝:看起来包含凭据的名称(*SECRET*、*TOKEN*、*API_KEY* 等),因为其值会写入结果;以及会改变执行内容的名称(LD_PRELOAD、BASH_ENV、NODE_OPTIONS、GIT_SSH_COMMAND 等),因为这些变量可能改变实际执行的代码。
运行器的固定变量和所有 PPV_* 名称也都是保留名称,不能转发。
在 argv 和每项检查的 env 值中展开的占位符:{checkout}(正在测试的修订版)、{plan_dir}(本次调用专用的计划产物隔离副本)、{scratch}(本次检查调用专用的全新不透明目录),以及
{side}(base 或 patched,且不适用于 exploit/variant 检查)。这些值也会通过 PPV_CHECKOUT、PPV_PLAN_DIR、PPV_SCRATCH、PPV_SIDE 和 PPV_CASE_ID 传入。只能在 {scratch} 下写入;检查不会收到证据目录路径。base 和 patched 调用不会共享由运行器管理的 scratch、计划或工作树根目录。每次调用退出后,其 scratch 树会归档到确定性路径 results/scratch/<check-id-and-side> 下,其私有计划副本会被丢弃;每个可读取且解析为文件的 argv 元素都会在 argv_files 中计算哈希。隔离计划或检出根目录中的文件还会保留在 results/helpers/<sha256> 下,最大为 16 MiB;记录会说明其他文件未归档的原因。请为 plan.json 使用专用目录:其同级文件和目录会复制到每次调用的 {plan_dir} 中。将辅助代码放在该目录的 checks/ 子目录中,或签入目标仓库,以便审查其字节内容。包含提交固定信息的机器计划、当前输出目录和检测到的既有结果树都不包含在内;符号链接会被拒绝。干净快照仅保留在运行器内存中,而
exploit/variant 侧会以随机顺序执行,证据文件名则保持确定。标准输出/错误使用匿名或随机命名的捕获描述符,并且只有子进程退出后才会复制到具名证据文件,因此检查文件描述符不会泄露侧标签。
这种隔离并非主机沙箱:检查以调用方权限运行,恶意辅助程序可能使用任意外部状态,或故意根据源代码或 Git 元数据推断修订版。请检查内容寻址的辅助产物;如果检查代码本身不可信,请使用操作系统/容器沙箱。
活动验证工作树会通过由内核文件锁支持的随机所有者令牌进行 Git 锁定,因此其他并发验证器无法清理这些工作树,PID 重用也无法冒充所有者。如果运行器被强制终止,下次运行会解除过期的验证器所有者注册。若需手动恢复,请检查 git worktree list,然后使用 git worktree unlock <path> 和
git worktree remove --force <path>(或者在路径已不存在后运行 git worktree prune)。
设计覆盖范围、选择变体或解读发现和验证缺口时,请阅读 evidence-model.md。常规 CLI 执行无需阅读该文档。
覆盖规则
- 根据根本原因推导变体,而不是对原始载荷做表面修改。
- 枚举修复涉及的同级调用点、备用回调/输出、错误路径、拆卸、所有权、序列化和边界值。
- 每个利用或变体测试都必须断言安全行为。它必须在有漏洞的基线上失败;在两个修订版本上都通过的测试无法证明修复有效。阅读基线侧的 stderr,并确认失败来自你编写的断言,而不是测试框架根本没有执行到相关代码。
- 将利用和变体断言限制在安全不变量上。将存活性、确切错误类型/消息、时序和兼容性单独作为
behavior或regression检查进行测试;否则,无关的契约变更可能会伪装成漏洞仍然存在的证据。 - 保持
control测试框架无害,并确保它能测试发生变更的组件。它用于证明测试框架在两个修订版本上都能正常工作。控制检查失败会留下缺口,并导致无法将其他失败归因于补丁。原始观察结果仍可供审查。 - 仅对应该保持不变的行为使用
behavior。有意进行精确输出比较;请将不稳定值放入确定性测试框架,而不是在文字中将其归一化后略去。 - 在将补丁后的失败视为新引入的问题之前,确保
security检查在有漏洞的基线上通过。基线检查失败时,归因仍无法确定。 - 验证期间不要编辑补丁。将失败交还给补丁作者,并启动全新的固定版本运行。
阅读结果
result.json schema 2.0 包含一个 assessment,其中有 status、findings、gaps 和
human_review_required。当所有必需检查都产生了可用证据时,状态为 complete,即使部分检查发现了失败。只要仍有任何缺口,状态就是 incomplete。Finding 会注明检查 ID、检查类型和未通过的预期结果。Gap 会注明检查 ID 和缺失的证据;对于清理失败等运行级问题,检查 ID 为 null。
套件检查会先在补丁后的修订版本上运行。检查完成但失败时,会在基线上运行相同的检查。如果基线通过,则报告补丁后的失败。如果两者都失败,则保留两份日志,并报告归因尚未确定。不要假设相同的退出代码意味着相同的失败。
所有检查均已完成且没有 finding 时,运行器退出码为 0;检查已完成但存在 finding 时为 1;验证不完整时为 10;输入无效时为 64。即使退出码为 10,也要阅读产物: 其中可能包含有证据支持的 finding,以及缺口。源码或构建证据无法证明运行时行为。报告任何通过结果时,也要注明证据级别。
Claude 动态工作流
Claude Code 将捆绑的工作流作为 /post-patch-validation:validate-patch 提供。
要通过 Workflow 工具传递结构化输入,请使用不带前导斜杠的名称:
Workflow({
name: 'post-patch-validation:validate-patch',
args: {
finding: '<finding text or local path>',
baseRef: '<vulnerable-ref>',
patchRef: '<patched-ref>',
workdir: 'post-patch-validation',
},
})
适当时使用 patchFile,而不是 patchRef。该工作流使用固定的覆盖视角来提出检查,并使用固定的执行器来运行此技能。Agent 可以编写测试产物。Python 运行器会记录 finding 和 gap,审查者则会分别报告覆盖范围或证据方面的问题。即使其他检查留下了缺口,NEEDS_REPAIR 仍会返回有证据支持的失败。BLOCKED 表示证据不完整,但没有有证据支持的失败。只有在两项证据审查都通过后,通过的检查才会进入 READY_FOR_HUMAN_REVIEW,否则状态为 REVIEW_REQUIRED。工作流启动后无法提问,因此请预先提供所有输入。
应予以驳回的托词
| 托词 | 必须采取的措施 |
|---|---|
| “原始 PoC 已经无法运行” | 至少测试一种独立的根因变体 |
| “漏洞利用在基线版本上失败了,所以复现成功” | 确认标记,并确认失败源于断言而非测试框架损坏 |
| “完整测试套件全部通过” | 明确证明基线复现和目标行为 |
| “这与上游/规范补丁一致” | 将来源背景视为参考信息,而非证据 |
| “差异很小” | 测试变更影响到的调用方、失败路径和拆卸过程 |
| “所有提供的检查都已通过” | 保留产物,并要求人工审核 |
| “一次有波动的重跑通过了” | 保留首次固定的结果;重试前先解决非确定性问题 |
| “没有明显的变体” | 检查同类位置和边界;否则报告缺失的覆盖范围 |