mcpField
P
post-patch-validation

post-patch-validation

github.com/trailofbits安全7.9k824 次安装今天更新
代码仓库

关于 post-patch-validation

post-patch-validation 用于验证安全补丁是否解决报告的问题,并检查周边行为是否受影响。它指导审查者针对固定版本运行可复现的本地检查,并汇总证据与覆盖缺口。

主要功能

  1. 01针对固定版本规划检查
  2. 02测试漏洞利用与根因变体
  3. 03检查行为、回归与安全性
  4. 04记录日志和验证证据
  5. 05报告发现与覆盖缺口

适用场景

  1. 01验证 AI 生成的安全补丁
  2. 02检查补丁可能遗漏的漏洞变体
  3. 03发现行为变更带来的回归
  4. 04为补丁修订提供可复现的失败案例

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/trailofbits/skills --skill post-patch-validation

安装后在 Claude Code 中输入/post-patch-validation确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

agents/
openai.yaml
references/
evidence-model.md
scripts/
post_patch_validation.py
pyproject.toml

详细介绍

补丁后验证

针对已报告的 Bug 及其影响的周边代码,验证安全补丁。为补丁作者提供可复现的失败案例以供修复,并指出仍需测试的内容。对人工编写的补丁和 Agent 编写的补丁执行相同检查。仅凭差异、作者、上游实现和原始概念验证,无法确定补丁正确。

适用场景

  • 已有安全修复、补救提交、补丁文件或拉取请求。
  • AI 生成的补丁需要在人工审查或合并前进行验证。
  • 修复可能覆盖了一条利用路径,却遗漏了同一根因的变体。
  • 安全修复可能改变正常行为或引入新的漏洞。
  • 补丁作者需要在进行下一轮修改前,获得具体失败案例和覆盖缺口。

不适用场景

  • 尚无补丁;请先发现漏洞或实现修复。
  • 任务是根据报告审查审计发现,但不执行补丁证据。
  • 任务仅是将某项发现转化为永久的项目测试。
  • 目标是远程或生产环境。本技能仅执行本地代码和测试。
  • 用户未授权执行仓库代码或测试套件。

快速开始

  1. 固定易受攻击的基线和已打补丁的输入。优先使用不可变提交。对于未提交的工作,请先创建二进制补丁文件;不要在用户的工作树中验证。

  2. 生成固定版本的计划:

    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。

  3. 检查发现、差异、调用方、同类路径、清理和错误处理路径,以及现有测试。在生成的计划中填写 checks。运行 print-schema 查看结构架构:

    uv run {baseDir}/scripts/post_patch_validation.py print-schema
    
  4. 在执行代码前,运行 validate-plan 完成验证,包括覆盖范围、命令限制和固定的输入:

    uv run {baseDir}/scripts/post_patch_validation.py validate-plan \
      --plan post-patch-validation/plan.json
    
  5. 执行证据计划:

    uv run {baseDir}/scripts/post_patch_validation.py run \
      --plan post-patch-validation/plan.json \
      --output post-patch-validation/results
    
  6. 提交 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 == '&lt;'"]

该标记也会通过环境变量 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 已经无法运行”至少测试一种独立的根因变体
“漏洞利用在基线版本上失败了,所以复现成功”确认标记,并确认失败源于断言而非测试框架损坏
“完整测试套件全部通过”明确证明基线复现和目标行为
“这与上游/规范补丁一致”将来源背景视为参考信息,而非证据
“差异很小”测试变更影响到的调用方、失败路径和拆卸过程
“所有提供的检查都已通过”保留产物,并要求人工审核
“一次有波动的重跑通过了”保留首次固定的结果;重试前先解决非确定性问题
“没有明显的变体”检查同类位置和边界;否则报告缺失的覆盖范围

评论

支持 Markdown