补丁后验证 是做什么的?
根据报告的错误及其影响的周围代码来验证安全补丁。为补丁作者提供可重现的失败点以供修复,并确定仍需测试的内容。对人力和AI补丁应用相同的检查。仅凭差异、作者、上游实现和原始概念验证不足以确定正确性。
使用时机
- 已有安全修复、修复提交、补丁文件或拉取请求。
- 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 数组,绝不能是字符串。将复杂的设置放入已检入或计划工件的脚本中,并使用 {plan_dir} 调用。运行器固定区域设置/时区/哈希种子输入,按词汇ID顺序执行检查,记录原始标准输出/标准错误,绝不编辑原始工作目录。每个检查的 timeout_seconds 默认为300,接受1到3600之间的整数。超时会导致验证缺口。超时本身并不构成回归。每个计划还包含一个排序的 submodules 数组(无子模块时为 [])。脚手架从更改文件清单中推断受影响的Git子模块链接。运行器从源存储库的现有Git模块对象初始化这些固定提交,绝不从 .gitmodules 网络URL初始化;在源存储库中初始化或获取它们,然后再进行验证。
漏洞利用和变体检查必须证明它们已运行
非零退出并不表示漏洞已重现。导入错误、构建失败、缺少依赖项以及失败的安全断言都退出非零,运行器无法区分它们。每个 exploit 和 variant 检查必须在两个修订版本上打印并刷新 PPV_REACHED,紧接在其评估断言之前:
"argv": ["python3", "-c", "import app; value = app.render('<'); print('PPV_REACHED', flush=True); assert value == '<';"]
该标记也作为环境变量 PPV_REACHED_MARKER 存在。它必须出现在标准输出上,作为单独一行。不扫描标准错误,因为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} 下;证据目录路径不会传递给检查。基础和补丁版本的调用不共享运行器管理的临时、计划或工作目录根。每次调用退出后,其临时目录树会归档到确定性路径 results/scratch/<check-id-and-side> 下,其私有计划副本会被丢弃,并计算每个可读的解析为文件的 argv 元素的哈希值记录在 argv_files。隔离的计划或检出目录内的文件还会额外保留在 results/helpers/<sha256> 下,最大 16 MiB;记录说明为什么任何其他文件未被归档。使用专用目录放置 plan.json:其同级文件和目录会复制到每次调用的 {plan_dir} 中。将辅助代码放在该目录下的 checks/ 目录或已检入到目标存储库中,以确保其字节可审查。包含提交固定、当前输出目录和检测到的先前结果树的机器计划被排除;拒绝符号链接。干净的快照仅保留在运行器内存中,并且漏洞利用/变体检查的侧面以随机顺序执行,而证据文件名仍然是确定性的。标准输出/标准错误使用匿名或随机命名的捕获描述符,并仅在子进程退出后复制到命名的证据文件,因此文件描述符检查无法披露侧面标签。
此隔离并非主机沙箱:检查以调用者的权限运行,恶意辅助代码可使用任意外部状态或从源代码或Git元数据有意推断修订版本。检查基于内容寻址的辅助工件,并在检查代码本身不受信任时使用操作系统/容器沙箱。
活动的验证工作目录通过由内核文件锁支持的随机所有者令牌进行Git锁定,因此另一个并发验证器无法清理它们,PID重用无法冒充所有者。如果运行器被强制杀死,下次运行将解锁过时的验证器注册。对于手动恢复,检查 git worktree list,然后使用 git worktree unlock <path> 和 git worktree remove --force <path>(或在路径删除后使用 git worktree prune)。
设计覆盖范围、选择变体或解释发现和验证缺口时,请阅读 evidence-model.md。常规CLI执行时无需阅读。
覆盖规则
- 从根本原因推导变体,而非原始有效载荷的表面变异。
- 列举修复涉及的兄弟调用点、备选回调/输出、错误路径、拆卸、所有权、序列化和边界值。
- 使每个漏洞利用或变体测试断言安全行为。它必须在脆弱基础版本上失败;在两个版本上都通过的测试对修复毫无证明意义。读取基础版本的标准错误并确认失败是您编写的断言,而非一个从未达到该点的测试脚本。
- 将漏洞利用和变体断言限制在安全不变性内。将活力、精确的错误类型/消息、时序和兼容性分别作为
behavior或regression检查进行测试;否则,不相关的合同更改可能伪装成漏洞仍然存在的证据。 - 保持
control测试脚本良性并使其执行更改后的组件。它确立了测试脚本在两个修订版本上均可工作。失败的控制检查留下缺口并阻止将其他失败归因于补丁。原始观察结果仍可用于审查。 - 仅对应保持不变的行为使用
behavior。有意进行精确输出比较;将不稳定的值移到确定性测试脚本后面,而不是在说明中将其规范化掉。 - 在将补丁上的失败视为新引入之前,确保
security检查在脆弱基础版本上通过。基线失败会留下未解决的归属问题。 - 不要在验证期间编辑补丁。将失败返回给补丁作者,并开始一个新的、新鲜固定的运行。
读取结果
result.json 模式2.0包含一个包含 status、findings、gaps 和 human_review_required 的 assessment。当所有必需检查都产生可用证据时,状态为 complete,即使某些检查发现了失败。当存在任何缺口时,状态为 incomplete。Findings 包含检查ID、检查类型和失败的期望。Gaps 包含检查ID和缺失的证据,对于运行级别的问题(如清理失败),检查ID为 null。
套件检查首先在补丁修订版本上运行。如果完成并失败,则触发对基线的相同检查。如果基线通过,则报告补丁后的失败。如果两者都失败,则保留两个日志并报告归属未解决。不要假设匹配的退出代码意味着相同的失败。
运行器对于没有发现的完整检查退出0,对于有发现的完整检查退出1,对于不完整的验证退出10,对于无效输入退出64。即使退出10也读取工件:它可以包含受支持的发现以及缺口。源代码或构建证据不能建立运行时行为。在通过结果旁边陈述证据级别。
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。工作流使用固定的覆盖透镜来提出检查,并使用固定的执行器来运行此技能。代理可以编写测试工件。Python 运行器记录发现和缺口,审查员单独报告覆盖或证据问题。NEEDS_REPAIR 即使在其他检查留下缺口时也会返回受支持的失败。BLOCKED 意味着证据不完整且没有受支持的失败。通过的检查仅在两种证据审查都批准后才升级到 READY_FOR_HUMAN_REVIEW,否则为 REVIEW_REQUIRED。工作流启动后无法提问,因此请预先传递所有输入。
应予拒绝的合理化说明
| 合理化说明 | 所需的拒绝措施 |
|---|---|
| “我通过查看代码确认了补丁是正确的。” | 需要运行证据计划并生成检查结果。 |
| “没有变体,漏洞利用是微不足道的。” | 仍需至少一个变体检查来证明对根本原因的覆盖。 |
| “行为检查会失败,因为输出不稳定。” | 不要规范化输出;设计一个确定性测试脚本。 |
| “补丁太小了,不需要全部检查。” | 即使很小的补丁也可能改变错误路径或引入新的调用者。 |
| “测试套件通过了,所以补丁是安全的。” | 测试套件可能没有覆盖补丁更改的路径。仍需特定检查。 |
…
[原文已截断,用于翻译] — 其余部分按相同模式翻译。