mcpField
M
mantis-patch

mantis-patch

github.com/google安全2.7k1.5k 次安装3 天前更新
代码仓库

关于 mantis-patch

mantis-patch 是 Mantis 安全流程中的修复技能,面向需要修补并验证安全问题的团队。它在隔离环境中生成和测试最小代码补丁;对于没有源代码的二进制或固件,则提出缓解建议。

主要功能

  1. 01生成最小安全补丁
  2. 02在隔离环境中应用和验证
  3. 03回滚临时修改
  4. 04更新发现记录与修复状态
  5. 05为二进制问题提供缓解建议

适用场景

  1. 01修复已复现的源代码漏洞
  2. 02在隔离环境中验证补丁
  3. 03为无源代码的二进制问题制定缓解方案

安装

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

$npx skills add https://github.com/google/mantis --skill mantis-patch

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

附带文件references/patch_rebasing.md

详细介绍

Patcher(/mantis-patch)

系统目标

安全补丁专家。生成最小且正确的代码修复,将修复应用到源代码文件,并在隔离沙盒中验证,然后将日志追加到长期记忆中。

命令定义

  • 命令: /mantis-patch [--target_root=<path>] [--state_root=<path>] [--snapshot_root=<path>] [--snapshot_id=<id>] [--snapshot_pinned=<true|false>]
  • **描述:**使用事务隔离(影子目录或文件备份)生成最小安全修复、应用补丁并进行验证。
  • 参数(均为可选;未提供 ⇒ 沿用今日行为):
    • --target_root:要读取的代码快照根目录(CODE_ROOT)。编排器固定快照时会传入 --snapshot_root;--target_root 保留给提供已准备好目录树的调用方使用。
    • --snapshot_root:本轮固定的不可变快照根目录。
    • --snapshot_id:固定快照的 SNAPSHOT_ID 字符串(用于快照匹配检查)。
    • --snapshot_pinned:若本轮存在固定的不可变快照,则为 true;false/未提供 ⇒ 降级/旧版模式(参见向后兼容)。
    • --state_root:包含 workspace/ 的 Mantis 状态目录根目录(默认为当前目录)。本文中的所有 workspace/... 路径均相对于状态目录,并解析为 <state_root>/workspace/...。

输入/输出契约

  • 读取:
    • workspace/findings/(已复现的发现 JSON 文件,其中 patch_status 不为 "VERIFIED_SECURE" 或 "MITIGATION_PROPOSED")。
    • workspace/.mantis_state.json(用于跟踪当前循环轮次)。
    • workspace/.mantis_state.json 中的 active_snapshot(root、snapshot_id、snapshot_pinned)—通过 Block A 解析;当未传入任何 --snapshot_* 标志时,用于提供 CODE_ROOT 和快照匹配检查所用的 SNAPSHOT_ID。
    • 目标源代码文件。
    • 发现中提供的复现脚本路径(repro_file_path)和命令(run_command)。
    • 与发现 ID 匹配的预先存在的备份文件(若使用选项 B)。
  • 写入:
    • 源代码修改(事务性应用并回滚)。
    • 原地更新发现 JSON 文件(设置 "patch_status"、"patch_diff"、包含 "reattack_status" 和 "reattack_variants" 的重新攻击详情,以及历史记录)。
    • 追加到 workspace/learnings.jsonl。
    • 可复用的辅助脚本 workspace/helpers/append_patch.py。
  • 前置条件:
    • workspace/findings/ 中必须存在发现。
  • 幂等性保证:
    • **感知快照的跳过逻辑:**仅当某个发现记录的 patch_base_snapshot 等于当前 SNAPSHOT_ID(即该发现已针对本快照验证)时,才跳过其 patch_status 已为 "VERIFIED_SECURE" 或 "MITIGATION_PROPOSED" 的情况。如果 patch_base_snapshot 缺失、为空或与当前 SNAPSHOT_ID 不同,则终态状态已过期 — 不要跳过;应重新打开该发现,并针对当前快照重新验证。在旧版模式(没有 active_snapshot 且未传入 --snapshot_* 标志)中,按今日行为,仅根据 patch_status 跳过。
    • 事务隔离:在唯一生成的临时目录中修改代码,或创建临时文件备份(target.c.bak-[id]),并在完成时恢复基线状态(使用 try...finally 回滚机制)。
    • 创建后复用现有的 append_patch.py 脚本。

说明

修复已成功复现的安全漏洞,同时不得破坏标准代码行为。

按以下步骤执行补丁和验证阶段:

  1. 快照与定位器解析(在接触任何代码前运行一次):

    0a. 通过 Block A(定位器解析)解析 CODE_ROOT、SNAPSHOT_ID、snapshot_pinned 和 state_root:

    LOCATOR RESOLUTION (before reading ANY target code or artifact):
    0. ROLE: If this skill NEVER reads target source (report, calibrate, reflect),
       you are a FINDINGS-ONLY stage: skip steps 2-6; still read active_snapshot from
       state for provenance/annotation; NEVER stop merely because a code root is unset.
    1. Determine CODE_ROOT, in this priority order:
       a. If --target_root is passed on THIS invocation, CODE_ROOT = --target_root.
          It is AUTHORITATIVE and OVERRIDES SNAPSHOT_ROOT and the state fallback
          (used when a caller hands you a prepared tree, e.g. a patched shadow).
       b. Else if --snapshot_root (or SNAPSHOT_ROOT) is passed, use it.
       c. Else read state_root/workspace/.mantis_state.json (state_root from
          --state_root if passed, else ./workspace/... relative to the current dir)
          -> active_snapshot.root / .snapshot_id / .snapshot_pinned.
       d. Else (no arg AND no readable active_snapshot): CODE_ROOT = current directory,
          treat snapshot_pinned = false (MODE-OFF). Do NOT stop.
    2. SENTINEL CHECK (only if snapshot_pinned is true AND you did NOT take path 1a):
       verify CODE_ROOT/.mantis_snapshot_id exists and equals SNAPSHOT_ID. If missing
       or different -> STOP "snapshot sentinel mismatch". (A --target_root tree (1a) is
       deliberately mutated and is sentinel-EXEMPT.)
    3. PATH FIELDS:
       - SNAPSHOT-RELATIVE (read under CODE_ROOT): code_paths entries; plan target_files
         that are file paths. Strip ONLY a trailing ":<digits>". A code_paths entry
         containing "://" is a URL/endpoint, NOT a file read. A code_paths entry that is
         NOT of the form <existing-path>:<integer> is a non-source LOCATOR
         (symbol/offset/endpoint): only check that the artifact/symbol exists; skip ALL
         line-range and line-existence logic.
       - STATE-RELATIVE (read/write under state_root/workspace, NEVER prefix CODE_ROOT):
         kb_references, repro_file_path, reattack_file_path, helper scripts, report
         files, and all state/findings JSON.
    4. Never WRITE under CODE_ROOT when snapshot_pinned is true. Any command that
       compiles, generates, or writes artifacts MUST run in a PRIVATE SHADOW copy
       (mktemp -d from CODE_ROOT), never with cwd=CODE_ROOT. Read-only inspection may
       cd into CODE_ROOT.
    5. VCS-METADATA CARVE-OUT: history-log extraction and any VCS diff/blame command
       run in the LIVE repository root (which still has .git/.hg/.repo), NOT CODE_ROOT
       (the snapshot copy strips VCS metadata). Do NOT stop merely because CODE_ROOT
       lacks .git/.hg/.repo.
    6. Every shell command uses ABSOLUTE paths and sets its own working directory on
       that call. Do NOT assume the working directory persists between calls.
    

    说明

    **当前轮次检查(防御性检查;具有约束力的保证由 mantis-pipeline-adapter 场景 2 中的测试框架提供):**如果存在 active_snapshot,且 active_snapshot.pass != state.pass_number,则将此快照视为本轮次的过期快照 — STOP "stale active_snapshot: pass mismatch",或降级为 HALT(snapshot_pinned 实际上为 false:没有权威判定,Block B 为 NOT_MATCHED,复现状态为 not_attempted)。 这可以捕获自定义测试框架在 Stage 15 轮次递增后保留 active_snapshot、却未重新固定快照的情况。参考元 Agent 每轮次都会重新固定快照,因此此检查不会在该流程中触发。Block B 本身无法检测到这一点(它只检查 snapshot_id,不检查 pass)。

    对于此阶段:mantis-patch 会读取目标代码(这不是仅处理发现项的阶段),因此会完整执行 Block A 的步骤 1–6。CODE_ROOT 是不可变的固定快照;你绝不能在其中写入、编译或运行复现程序(Block A 步骤 4)— 请在私有影子副本中执行所有修改(见下方步骤 2)。

    0b. 确定 PATCH_MODE(根据解析后的值机械判定):

    • 未提供 active_snapshot,且未传入 --snapshot_root/--snapshot_id/ --target_root ⇒ LEGACY 模式(沿用当前行为;步骤 3 中的新上限不会触发)。
    • snapshot_pinned == true ⇒ PINNED 模式(适用以下完整门槛)。
    • snapshot_pinned == false,且 SNAPSHOT_ID 以字面值 live: 开头 ⇒ HALT/DEGRADED 模式(曾尝试固定快照,但未能固定; 本轮次禁止给出权威判定 — 见步骤 3)。

    0c. 对于你处理的每个发现项,通过 Block B(快照匹配检查)计算其 MATCHED/NOT_MATCHED 结果:

    SNAPSHOT MATCH CHECK for finding F (decides MATCHED vs NOT_MATCHED):
    1. If snapshot_pinned is false -> NOT_MATCHED. Stop.
    2. Read F.discovery_commit:
       - missing OR empty OR the literal "MIXED" -> NOT_MATCHED.
       - not exactly equal to SNAPSHOT_ID          -> NOT_MATCHED.
       - exactly equal to SNAPSHOT_ID              -> MATCHED.
    There is no other route to MATCHED; never fuzzy-compare. The global "default the
    field and proceed" backward-compat rule does NOT apply to discovery_commit:
    absent = NOT_MATCHED. (There is NO separate "dirty" gate: a dirty tree's
    SNAPSHOT_ID already embeds the working-tree content hash, so within-pass findings
    MATCH and cross-pass bare-commit findings do not.)
    

    此 MATCHED/NOT_MATCHED 结果会在步骤 2(隔离模式选择)和步骤 3(判定)中使用。它本身不会在 PINNED 模式下降低判定等级:NOT_MATCHED 的发现项会通过步骤 3 的门槛,针对当前快照重新验证;如果通过验证,则会以当前 patch_base_snapshot 加注。

  2. **加载待修补的发现项:**读取 workspace/findings/ 目录中的 JSON 文件。筛选出尚未针对当前快照完成终结验证的发现项 — 也就是说,除非某发现项的 patch_status 为 "VERIFIED_SECURE" 或 "MITIGATION_PROPOSED",且其 patch_base_snapshot 等于当前 SNAPSHOT_ID(“快照与定位符解析”步骤 0),否则都将其视为仍需处理。patch_base_snapshot 缺失、为空或不同的终结发现项均为过期项 — 将其纳入重新验证。(在没有 SNAPSHOT_ID 的 LEGACY 模式下,按原样仅根据 patch_status 进行筛选。)在纳入的发现项中,保留满足以下任一条件的项:repro_status 为 "reproduced",或该发现项属于漏洞利用链,例如标题以 "Exploit Chain:" 开头、历史记录中有来自 "chainer" 阶段的条目,或存在非空的 "constituent_findings" 属性。如果没有符合条件的发现项,请通知用户。

  3. **生成并应用最小补丁:**对于每个已复现的安全缺陷:

    • **不依赖目标类型(二进制与源代码):**如果目标是源代码,请按下文所述生成并应用代码补丁。如果目标是没有可用源代码的已编译二进制文件或固件 blob,不要尝试修改二进制文件,也不要编写二进制修补脚本。请改为跳过分支隔离、修改和差异步骤,并生成一条概括性较强的建议,说明如何在生产环境中缓解此问题,且无需深入技术细节。将此缓解措施字符串输出为 patch_diff 字段的内容,并将 "patch_status" 设为 "MITIGATION_PROPOSED"。

    • 利用链: 如果该发现属于利用链(标题或历史详情中标有 "Exploit Chain:",或存在非空的 "constituent_findings" 属性),则不要 生成代码补丁或差异。应改为读取 "constituent_findings" UUID 数组,找出其子发现。监控这些组成发现的补丁状态(磁盘上的文件路径为 workspace/findings/<uuid>.json)。重要: 请等到批次中的所有单项发现均处理完毕后,再评估利用链,以确保磁盘上可获取其组成发现的最新补丁状态。

      按以下传播规则(依次评估)评估利用链状态:

      1. 有效性检查: 读取每个组成发现的有效性 "status"。如果任一组成发现的 "status" 为 "FALSE_POSITIVE",则将利用链发现的 "status" 更新为相同值(例如 "FALSE_POSITIVE"),并立即跳过该链的所有后续补丁处理与验证。如果任一组成发现的 "status" 为 "DUPLICATE",则通过递归追踪其 "duplicate_of" 属性,将其解析为规范发现。

        重复项解析流程:

        1. 定位发现文件: 重复项(或重复链中的任一上级)的发现文件可能已被移动。请按以下顺序搜索 <uuid>.json:
          • workspace/findings/<uuid>.json(活动发现)
          • workspace/findings/.trash/<uuid>.json(去重后移入垃圾箱的发现)
          • workspace/archive/findings_pass_*/<uuid>.json 或 workspace/archive/loop*_findings/<uuid>.json(先前轮次的归档文件)。如果在这些位置均找不到文件,则视为文件缺失错误。
        2. 循环检测: 在解析过程中维护一组已访问的发现 UUID。如果遇到当前解析链中已访问过的 UUID,则引发验证错误(检测到循环)。
        3. 最大深度: 将递归深度限制为最多 5 步。如果链更深,则中止并报告错误。
        4. 提取状态: 解析到规范发现后(其 "status" 不是 "DUPLICATE",或没有 "duplicate_of"),在所有后续检查和传播中使用该规范发现的 "status" 和 "patch_status"。不要将利用链发现本身更新为 "DUPLICATE"。
      2. 文件缺失: 如果磁盘上缺少任一组成发现的 JSON 文件,则将该链的 "patch_status" 设为 "ERROR"。

      3. 组成发现未设置或已过期(待处理): 如果组成发现的 "patch_status" 未设置(为 null 或缺失,表示尚未复现或处理),或其 "patch_status" 已是终态,但其 patch_base_snapshot 缺失、为空或与当前 SNAPSHOT_ID 不同(基于“快照与定位符解析”步骤 0 验证,该状态是在过期快照上验证的),则该组成发现视为尚未验证。如果任一组成发现尚未验证,则该链的 "patch_status" 必须保持未设置(为 null 或缺失),并且必须延后或暂停对该链的进一步评估,直到该组成发现已在当前快照上重新验证。(在没有 SNAPSHOT_ID 的 LEGACY 模式下,仅应用本规则中“未设置”的部分,与之前完全相同。)

      4. 组成发现出错: 如果任一组成发现的 "patch_status" 为 "ERROR",则将该链的 "patch_status" 设为 "ERROR"。

      5. 组成发现验证失败: 如果任一组成发现的 "patch_status" 为 "VERIFICATION_FAILED",则将该链的 "patch_status" 设为 "VERIFICATION_FAILED"。

      6. 成功传播: 如果所有组成项均已针对当前快照完成验证(每项状态均为 {"VERIFIED_SECURE", "MITIGATION_PROPOSED", "VERIFICATION_INCOMPLETE"} 中的一种,且除 LEGACY 模式外,每个终端组成项的 patch_base_snapshot 都等于当前 SNAPSHOT_ID):

        • 如果任一组成项为 "MITIGATION_PROPOSED",将链的 "patch_status" 设为 "MITIGATION_PROPOSED"。
        • 如果没有组成项为 "MITIGATION_PROPOSED",且任一组成项为 "VERIFICATION_INCOMPLETE",将链的 "patch_status" 设为 "VERIFICATION_INCOMPLETE"。
        • 如果所有组成项均为 "VERIFIED_SECURE",将链的 "patch_status" 设为 "VERIFIED_SECURE"。

      跳过针对链发现本身的分支隔离、测试和重新攻击步骤。

    • 可选的并行方案搜索: 如果你的框架支持子 Agent,可以启动多个并发子 Agent 来设计不同的补丁实现。测试所有能够成功保障代码安全且不破坏标准功能的 补丁,并选择最佳补丁(例如最精简、易读且符合惯例的修复方案),而不是 仅选择第一个可用的方案。

    • 阅读有缺陷的原始文件,以了解函数依赖关系和结构。

    • 设计最精简且正确的补丁来缓解安全缺陷(例如添加边界检查、验证大小、插入 NUL 终止符),同时不破坏其他功能。

    • 事务隔离(与 VCS 无关且安全): 为确保安全、可靠并与 VCS 无关,不要 使用基于 VCS 的分支操作(例如 git branch、git checkout 或 git stash)。

      你必须使用适合运行环境的方法确保事务隔离。隔离方法门控(机械规则):

      • 如果 snapshot_pinned 为 true(PINNED 模式)→ 必须使用选项 A(临时目录 镜像)。禁止使用选项 B,以及任何会在 CODE_ROOT 下写入内容的方法(Block A 第 4 步:绝不在固定快照下写入)。
      • 否则,如果此发现属于 RETRY(已有先前的 "patch" 历史记录或 patch_base_snapshot),或者(PINNED 模式且其快照匹配检查结果为 NOT_MATCHED)→ 必须使用选项 A(不要冒险编辑已不再与该发现匹配的活动目录树)。 NOT_MATCHED 条件中的 PINNED 模式限定至关重要:在 LEGACY 模式(snapshot_pinned=false) 下,Block B 会对每个发现返回 NOT_MATCHED,这是因为不存在快照,而非检测到目录树发生变化。 因此,只有当 NOT_MATCHED 表示“快照已更改”(PINNED 模式)时才应触发此条件,不能在其表示 “不存在快照”(LEGACY 模式)时触发。

      否则(snapshot_pinned 为 false 且是首次尝试——没有先前的 "patch" 历史记录,也没有 patch_base_snapshot),你可以选择选项 A(推荐)、选项 B:文件级备份,或 设计/实现选项 C:替代隔离方式(例如命名空间隔离、容器卷或本地沙箱),前提是 完全满足以下不变量。这适用于 LEGACY 和 HALT 模式下的首次尝试发现(两者都没有先前的补丁需要 保护,也都不处于 PINNED 只读快照模式)。

      无论选择哪种方法,都必须保证以下不变量:

      1. 工作区零污染: 原始源代码树中不得遗留备份文件或中间构建文件。
      2. 并发安全: 隔离方法不得与其他并发 Agent 冲突。
      3. 保证回滚: 使用错误捕获机制或 try...finally 块包装所有操作,以便失败时恢复原始状态。
      • 选项 A:临时目录镜像(推荐/固定快照时必须使用)

        • 警告/资源限制: 对于大小超过几百 MB 的源代码树,或者有许多并行补丁工作进程共用主机时,且仅当不处于 PINNED 模式时,你可以优先选择选项 B(仅处理已修改的文件),以避免耗尽 /tmp 空间或内存。在 PINNED 模式下,无论大小如何,都禁止使用选项 B(快照为只读);如果无法容纳完整副本,不要编造结论——请参阅步骤 3 的 HALT 处理。

        • 双根目录副本: 在 CODE_ROOT 和任何 /workspace/ 路径之外,创建一个唯一生成的 SHADOW_ROOT(例如使用 mktemp -d / tempfile.mkdtemp()),然后将相关源代码树从 CODE_ROOT(即“快照与定位符解析”步骤 0 中解析出的快照)复制到 SHADOW_ROOT,不要从隐式 cwd 复制。CODE_ROOT 必须保持不变且只读。

        • 所有编辑、编译和复现测试都必须在 SHADOW_ROOT 中进行。绝不要在 cwd = CODE_ROOT 时运行任何会修改文件、编译或复现问题的命令(区块 A 步骤 4)。

      • 关键防护(路径与工作目录安全): 必须确保执行的每条命令(编译、测试、验证)都将工作目录(Cwd)明确设为 SHADOW_ROOT。如果问题中的 run_command 包含位于 CODE_ROOT(快照)或活动源代码根目录下的绝对路径,执行前必须将这些路径前缀改写为 SHADOW_ROOT 下对应的路径。

        防护检查: 改写路径时,只改写指向目标代码库文件的路径。如果路径以 /workspace/ 段开头或包含该段(Mantis 状态和问题存储位置),则不要替换其前缀(问题/状态必须保留在权威的 state_root/workspace/ 中)。两者的界限明确无歧义,因为固定的 SNAPSHOT_ROOT/CODE_ROOT 绝不会包含 /workspace/ 段(由编排器的快照物化过程保证)。不要对 CODE_ROOT 或 state_root/workspace/ 执行任何修改或验证命令。

      • {TARGET_ROOT} 令牌替换(执行任何已存储命令前均为强制要求): mantis-reproduce 会在 run_command/reattack_run_command 中用字面令牌 {TARGET_ROOT} 表示目标树路径(复现步骤 3),并期望调用方进行替换。执行任何已存储的 run_command 或 reattack_run_command 前,必须将字面令牌 {TARGET_ROOT} 替换为正在执行操作的树:

        • 对于区块 G 的未打补丁基线运行:替换为从 CODE_ROOT 复制得到的全新未打补丁副本(通过 mktemp -d 创建的新副本)。
        • 对于补丁后验证和攻击/重新攻击运行:替换为 SHADOW_ROOT(已打补丁的影子副本)。在进行上述 CODE_ROOT→SHADOW_ROOT 前缀改写之前,必须先完成此替换(该令牌可能会展开为 CODE_ROOT 路径,之后在影子运行中仍需改写)。如果字面令牌未被替换,命令将失败 → 证据缺失 → 强制判定为 ERROR(区块 F/区块 G 将 command-not-found/No-such-file 视为 EVIDENCE ABSENT)。绝不要执行包含字面 {TARGET_ROOT} 令牌的命令。
      • 通过比较 CODE_ROOT(固定快照)中的原始源文件与 SHADOW_ROOT 中已修改的文件,生成统一补丁差异。使用标签,避免在差异标头中泄露 CODE_ROOT/SHADOW_ROOT 绝对路径前缀。

      • 完成后,彻底删除临时影子目录。

      • 选项 B:文件级备份(备用方案)

        • **并发与互斥警告:**由于选项 B 会直接修改原始工作区中的文件,因此与其他修改工作区的 Agent 并行运行时不具备并发安全性。必须通过锁严格确保按顺序执行。
        • **独占工作区锁:**在执行备份或编辑之前,Agent 必须获取 workspace/.workspace_edit.lock 的独占锁(在 Python 中使用 fcntl.flock 和 fcntl.LOCK_EX,或使用类似的系统级锁)。在针对该问题的整个补丁应用、验证、重新攻击和恢复过程中,Agent 必须始终持有此锁;只有在最终基线文件恢复或定稿后,才能释放该锁。
        • **执行前检查:**编辑前,扫描工作区,查找与当前问题 ID 匹配的现有备份文件(例如,*.bak-[current_finding_id])。如果找到,则恢复并删除这些文件。
        • **创建备份:**为每个准备修改的源文件创建一个带唯一后缀的副本(例如,cp target.c target.c.bak-[finding_id])。
        • **新增文件:**跟踪所有新建文件,以便在回滚时删除。
        • **应用修改:**直接编辑原始目标文件。
        • **生成统一差异:**使用标签规范化文件头,将备份文件与修改后的文件进行比较(例如,diff -u --label target.c --label target.c target.c.bak-[finding_id] target.c)。
    • **三方补丁变基(第 2 阶段的增量效率):**如果磁盘上仍保留前一轮的快照和补丁(见下方的可达性检查),补丁器可以尝试将前一轮的补丁变基到当前快照上,而不是从头生成新补丁。这是在先前修复仍然适用、仅行号略有变化这一常见情况下的优化。

      • 方式: 如果以下可达性条件(a)-(d)全部满足,请使用文件读取工具阅读 3-Way Patch Rebasing,了解三方合并机制(base/ours/theirs 临时副本、git merge-file / diff3 的调用方式以及冲突标记处理)。如有任何不确定之处,或无法加载该参考资料,请回退到重新生成补丁(Phase-1 行为)。
      • 使用时机(依据可观察状态判断可达性,而非补丁器无法读取的标志): 仅当以下条件全部满足时,才尝试变基:(a) 上一轮的快照目录和该文件未打补丁的先前版本确实存在于磁盘上的 <state_root>/.mantis_snapshots/ 中(保留策略保留了它们;如果基础快照已被 GC,则此条件不满足 → 回退);(b) 该问题的 signature 与一个带有先前 patch_diff 的归档问题相匹配(同一 bug、相同签名)——此归档问题是后续条件所指的归档问题;(c) 该归档问题的 patch_base_snapshot 存在,且不同于当前的 SNAPSHOT_ID(先前补丁所针对并通过验证的快照已发生变化;这是正确的“快照已更改”判定——不要检查当前问题的 Block B,因为在 PINNED 模式下,当前问题的 discovery_commit 始终 == SNAPSHOT_ID:研究人员会为其添加标记,去重流程会补全该值,而 plan 只会逐字复制匹配的问题并保留原始 discovery_commit);如果归档问题的 patch_base_snapshot 缺失或为空(先前轮次为旧版流程,或 Phase-2 前的补丁器未写入该值——由步骤 6 的 helper-version 标记检测),则无法确定快照是否已更改,必须回退到重新生成补丁(不要在可能未更改的基础上变基);以及 (d) 该问题的主文件位于 changed_files 中。如果 signature 缺失(旧版问题),则无法满足条件 (b)——回退到重新生成补丁。不要以 --snapshot_keep 为判断条件:该标志由编排器设置,不会传递给补丁阶段,也无法由其读取;检查文件是否存在于磁盘上(a)是正确的可观察替代方式,适用于任何保留设置(默认的 keep-2 已会保留紧邻的上一轮)。changed_files 和 changed_files_status 从 workspace/.mantis_state.json 读取(与读取 active_snapshot 时使用的是同一个状态对象)。
    • 变基成功 / 失败 / 防护措施: 关于成功(干净合并 → 使用变基后的补丁,仍运行 Block G,历史记录备注 patch-rebased-from: pass_<N-1>)、失败(合并冲突 / 文件已删除 / 已重命名 → 回退到重新生成补丁)以及防护措施(如有任何不确定之处,回退到 Phase-1;绝不应用存在未解决冲突或无关更改的变基补丁),请参阅 3-Way Patch Rebasing。

    • 在 --reattack 中重新运行未打补丁基线(Phase 2): 当 @mantis-reproduce --reattack 子 Agent 在新快照上重新攻击补丁时(Block B 为 NOT_MATCHED,表示自补丁通过验证以来快照已发生变化——仅限 PINNED 模式),重新攻击前必须先在当前快照上重新确立未打补丁的基线,然后再针对打过补丁的构建测试攻击。这可确保重新攻击有意义:如果当前未打补丁的快照已不再触发该 bug(例如,上游代码已修复),则重新攻击的结果应判定为无法确定,而非通过。

      • 方式: 在对已修补的影子副本发起攻击之前,先针对当前快照的全新未修补副本运行复现程序(按照 Block G 步骤 1)。如果未修补基线未触发(没有证据),则将 reattack_status = "inconclusive_baseline_changed",并添加历史记录说明;不要声称 failed_to_bypass(该补丁并未经过针对真实漏洞的测试)。如果未修补基线触发,则照常对已修补版本发起攻击。
      • HALT 模式说明: 在 HALT 模式(snapshot_pinned=false,live: SNAPSHOT_ID)下,Block B 始终为 NOT_MATCHED,但这并不代表快照确实发生了变化——这表示无法固定树状态。@mantis-reproduce --reattack 子 Agent 会在 HALT 模式下跳过 C5 基线复测(参见 reproduce C5 HALT guardrail),并遵循现有的 HALT 上限。
      • 护栏: 这不会改变 VERIFIED_SECURE 门槛——Block G 仍要求未修补基线触发。这只会在快照发生变化时,为重新攻击流程增加一个基线复测步骤。
  4. 补丁后验证运行: (对于未应用代码补丁的仅二进制目标,跳过此步骤。) 为确认补丁有效,请在隔离执行环境中重新运行复现脚本。使用复现记录中的确切 "repro_file_path" 和 "run_command" 验证补丁。

    • 工作目录强制要求: 必须将工作目录(Cwd)设为影子目录来执行复现脚本(如果使用选项 A)。确保命令针对的是影子目录中的副本,而非原始工作区。

    • 到达 sink 的证据定义 — Block F(到达 Sink 证据门槛)。在补丁验证中,必须在未修补基线上提供证据,绝不能在补丁后的攻击运行中提供证据(正确的补丁会在输入到达 sink 前将其拦截):

      到达汇点证据门槛(机械判定):
      每个复现用例都通过一个渠道生成到达汇点证据,并记录在 repro_hints 中:
         (a) 脚本/源代码测试框架 -> 在调用汇点之前,将确切字节 MANTIS_REACHED_ENTRYPOINT 写入旁路文件 $SENTINEL_FILE,并执行 flush+fsync(或使用无缓冲写入)。(发生崩溃导致缓冲的 stdout 被截断时,文件仍会保留。)
         (b) 二进制文件 / 固件 / 原始负载 -> 到达汇点的证据是捕获到的崩溃回溯或消毒器帧(ASan/UBSan/MSan/TSan),其中明确指出目标汇点函数(由目标生成的跟踪)。你编写的包装器在调用目标之前写入的标记仅属于设置证据:它只能证明“尝试启动”,不能证明“已到达汇点”,因此不符合到达汇点证据的要求。如果无法实现路径内标记(渠道 a),也无法实现由目标生成的回溯/消毒器跟踪(渠道 b),则视为未到达汇点。
      存在证据(已到达汇点)=(渠道 a)旁路文件包含在路径内写入的 MANTIS_REACHED_ENTRYPOINT,或(渠道 b)由目标生成的回溯/消毒器输出指出汇点。仅有包装器启动前标记不属于证据。
      证据缺失包括:任何非零编译/构建退出状态;退出状态为 127(找不到命令);退出状态为 2,且显示“No such file”消息。
      判定门槛(门槛针对判定结果,而非特定判决字符串):
        - 仅当证据存在时,才将 repro_status 记录为 reproduced 或 statically_confirmed。若证据缺失 -> repro_status = not_attempted(可重试),停止。
        - 在补丁验证中,未打补丁的基线(Block G)必须有证据;补丁后的攻击运行不需要证据(正确的补丁可能会在输入到达汇点之前将其拦截)。
        - 如果无法为此目标实现任何证据渠道,则降级为 not_attempted / VERIFICATION_INCOMPLETE。绝不可伪造标记。
      
    • 任何 VERIFIED_SECURE 之前的硬性门槛 — Block G (未打补丁基线 + 良性控制门槛)。在 CODE_ROOT 的 SHADOW 副本中运行;Block G 步骤 1 中的“SNAPSHOT_ROOT 的全新未打补丁副本”指从 CODE_ROOT 取得的全新副本:

      未修补基线 + 修补后门禁(在任何 VERIFIED_SECURE 之前):
      0. 适用范围:仅当 repro_status == reproduced(存在可运行的 PoC)时适用。如果 repro_status == statically_confirmed(不存在可运行的 PoC),则 VERIFIED_SECURE 不适用 → 记录 MITIGATION_PROPOSED(绝不要记录 ERROR)。
      1. 未修补基线:使用 Block F,对 SNAPSHOT_ROOT 的全新未修补副本运行复现程序。如果没有证据,或未触发漏洞,则 patch_status = ERROR("reproducer_invalid_on_current_snapshot");不要设置 VERIFIED_SECURE。
      2. 修补后完整性(区分“被修补程序阻止”和“构建失败”):
         - 必需:修补后的构建必须成功编译,退出码为 0。退出码非 0 → VERIFICATION_INCOMPLETE。
         - 必需(良性对照):通过修补后的构建运行一个合法的良性(非攻击)输入;它必须到达 sink 路径(通过 Block F 确认存在证据),且不发生崩溃。这可以证明修补缩小了漏洞影响范围,而非破坏测试框架/入口点。如果无法为此目标构造良性对照,则不得仅凭干净运行就声称 VERIFIED_SECURE → 设为 VERIFICATION_INCOMPLETE,并记录无法构造良性对照的原因。(严格模式:干净编译 + 干净运行,但没有良性对照到达 sink,绝不足以认定 VERIFIED_SECURE。)
      3. 对修补程序发起攻击:通过修补后的构建运行攻击输入。要认定 VERIFIED_SECURE,必须同时满足:步骤 1 在未修补版本上触发(存在证据)、步骤 2 完整性检查通过、步骤 3 攻击未导致崩溃或触发漏洞,且 `--reattack` 运行也无法绕过修补程序。否则为 VERIFICATION_FAILED。
      

      Sanitizer 一致性防护规则(Block G): 此门禁中的所有运行(未修补基线、良性对照、攻击和重新攻击)都必须使用触发未修补基线时相同的 sanitizer 标志。如果基线通过 UBSan 触发(退出码为 0,恢复模式),攻击运行也必须使用 UBSan 编译;否则,即使漏洞仍然存在,攻击也可能以退出码 0 结束且不留下跟踪信息,从而错误地判定为 VERIFIED_SECURE(违反 INV-1)。将使用的 sanitizer 标志记录在 repro_hints 中,以便后续阶段复现。

    • HALT/DEGRADED 上限: 如果 PATCH_MODE 为 HALT/DEGRADED(snapshot_pinned 为 false,且 SNAPSHOT_ID 为 live:),则本轮禁止给出权威判定:patch_status 最高只能为 "VERIFICATION_INCOMPLETE"。此模式下绝不能输出 "VERIFIED_SECURE"。 (LEGACY 模式不受影响。)

    • 文件缺失规则: 如果尝试验证时找不到复现程序文件(repro_file_path)或 code_paths 中的目标文件,则将 patch_status 设为 "ERROR"。绝不要将文件缺失视为“未复现”,也绝不要因文件缺失而输出 "VERIFIED_SECURE"。(这也是 Block F/Block G 中“No such file”/exit 127 → EVIDENCE ABSENT 路径所强制执行的规则。)

    • VERIFIED SECURE(以下所有条件均为必需): 仅当以下条件全部满足时,才将 patch_status 标记为 "VERIFIED_SECURE":

      1. Block G 全部通过: 未打补丁的基线在 CODE_ROOT 的全新副本上,凭借已触达汇点的证据(Block F)触发了漏洞;打过补丁的构建编译成功(退出码为 0);良性/完整性控制有效;并且打过补丁的构建不会因攻击输入而崩溃/触发漏洞。
      2. 重新攻击无法绕过: 一个全新且独立的 @mantis-reproduce --reattack 子 Agent(按下方说明启动)写入 reattack_status = "failed_to_bypass"——这表示非空的 reattack_variants 数组中包含 ≥ 3 个有效变体输入(复现步骤 3a),且这些输入在已打补丁的影子副本中都未能触发原漏洞类别。变体集合为空或数量不足时,状态上限为 VERIFICATION_INCOMPLETE,绝不能是 VERIFIED_SECURE。
      3. 未降级: PATCH_MODE 不是 HALT/DEGRADED。如果 PATCH_MODE 为 HALT/DEGRADED(快照不匹配/无法固定快照),状态上限为 "VERIFICATION_INCOMPLETE",绝不能是 "VERIFIED_SECURE"。如果 Block G 报告无法建立基线(缺少证据,或当前未打补丁的快照不会触发漏洞),将 patch_status 设为 "ERROR"("reproducer_invalid_on_current_snapshot")——切勿输出 "VERIFIED_SECURE"。如果 Block G 的适用性检查发现 repro_status == statically_confirmed,则记录 "MITIGATION_PROPOSED"(绝不能记为 ERROR)。为确保真正独立,应针对已打补丁的影子副本启动一个全新的 @mantis-reproduce --reattack --finding_id=[finding_id] 子 Agent(该副本是应用补丁后的 SHADOW_ROOT 副本)执行重新攻击。

      重要: 调用 @mantis-reproduce 子 Agent 执行重新攻击时:

      • 如果使用 选项 A(影子副本)(在 PINNED 模式下始终如此),请传入:
        • --reattack
        • --target_root=<PATCHED_SHADOW_ROOT>——包含已应用补丁的影子副本。由于 --target_root 具有权威性且不受哨兵检查限制(Block A 步骤 1a/2),子 Agent 会直接读取已打补丁的树。
        • --snapshot_pinned=false——已打补丁的影子副本经过刻意修改,因此复现子 Agent 必须跳过该副本的快照哨兵/匹配检查。
        • --state_root=<state_root>——权威状态目录(因此所有 reattack_* 字段和历史记录都写入 state_root/workspace/)。
        • 正在验证的发现对应的 --finding_id=[finding_id]。
      • 如果使用 选项 B(备份)或选项 C(替代方案)(仅 LEGACY 模式可用),请传入 --reattack,并将两个根目录都指向实时工作区(或将它们留为默认值 .),以及 --finding_id=[finding_id]。

      使用 --reattack 运行的复现 Agent 会将结果直接写入原始工作区发现记录(state_root/workspace/findings/)中主要发现对应的 reattack_status、reattack_file_path、reattack_run_command 和 reattack_output 字段,同时保持初始的 repro_* 字段不变。

    • 验证失败: 如果沙箱执行仍会触发漏洞,或者重新攻击成功绕过了补丁,则说明补丁不充分。请重新评估并调整修复方案。

    • 验证未完成: 如果初次补丁后验证运行通过,但后续重新攻击检查因沙箱基础设施错误、环境超时或平台限制而失败/超时,请将 "patch_status" 设为 "VERIFICATION_INCOMPLETE"。

    • 未设置 re-attack 状态 / SNAPSHOT_MISMATCH → VERIFICATION_INCOMPLETE: 如果 reattack_status 未设置(re-attack 子 Agent 留下了未设置的状态,并附有 SNAPSHOT_MISMATCH 或 setup_failed 历史备注,或者根本未运行 re-attack),则将 patch_status 设为 "VERIFICATION_INCOMPLETE",绝不能设为 VERIFIED_SECURE。re-attack 状态未设置意味着尚未确认其独立性;SNAPSHOT_MISMATCH 则意味着 re-attack 所针对的快照与验证补丁时使用的快照不同。无论哪种情况, 补丁都未得到确凿验证。

    • 基线结果不确定 → VERIFICATION_INCOMPLETE: 如果 reattack_status 为 "inconclusive_baseline_changed"(re-attack 子 Agent 在当前快照上重新运行了 未打补丁的基线,但漏洞不再触发——自补丁验证以来基线已发生变化),则将 patch_status 设为 "VERIFICATION_INCOMPLETE"(或者设为 "ERROR",并附上详细信息 "reproducer_invalid_on_current_snapshot"),绝不能设为 "VERIFIED_SECURE"。 补丁未在当前快照上针对实际存在的漏洞进行测试;声称补丁安全会造成具有误导性的权威判定。

  5. 提取补丁并回滚事务: (仅二进制目标可跳过此步骤)。不要让代码库处于被更改的状态。 一旦得出最终结果(即 VERIFIED_SECURE,或已用尽重试次数):

    • 如果成功,生成表示确切更改内容的统一差异,并将其保存到 "patch_diff" 字段:
      • 如果使用选项 A(影子副本),通过比较 CODE_ROOT 中的原始文件与影子 目录中的修改后文件来生成此差异。
      • 如果使用选项 B(备份),通过比较备份文件与修改后文件来生成此差异,并明确标注文件头, 以免备份后缀出现在文件头中(例如: diff -u --label target.c --label target.c target.c.bak-[finding_id] target.c)。
      • 如果使用选项 C(替代方案),生成干净且与 VCS 无关的统一差异,用于比较未修改的基线文件与最终修补后的文件。
      • 如果修改了多个文件,则分别生成统一差异,并将其整齐地拼接到单个 "patch_diff" 字符串中。不要使用特定于 VCS 的差异命令。
    • 事务性清理 / 回滚:将代码库恢复至原始状态。
      • 如果使用选项 A(影子副本),彻底删除临时影子目录(例如, rm -rf <shadow_directory>)。由于原始代码库从未被修改,因此无需进一步恢复。
      • 如果使用选项 B(备份),将备份文件复制回目标文件,以恢复原始文件(例如, cp target.c.bak-[finding_id] target.c),删除备份副本 (rm target.c.bak-[finding_id]),并删除修补过程中创建的所有新增文件。不要 删除写入 workspace/reproducers/ 目录的任何 re-attack/PoC 脚本文件、写入 workspace/helpers/ 目录的任何辅助脚本,或记忆数据库文件 workspace/learnings.jsonl; 必须明确保留这些文件。
      • 如果使用选项 C(替代方案),执行相应的拆除或回滚步骤,彻底清除所有修改产物,删除所有临时资源, 并确保原始工作区恢复到干净的基线状态。
  6. 追加到长期记忆(持续审查链接): 对于处理的每个安全缺陷,以追加模式将一行结构化 JSON 追加到 名为 workspace/learnings.jsonl 的工作区数据库文件中。这样,策略制定者(/mantis-plan)便可在后续轮次中读取这些历史记录,避免为已修补的文件提出修复方案。

    • 记忆条目格式: {"title": "[security_flaw_title]", "code_paths": ["[path1:line1]"], "status": "[VERIFIED_SECURE / MITIGATION_PROPOSED / VERIFICATION_INCOMPLETE / VERIFICATION_FAILED / ERROR]", "patch_base_snapshot": "[current SNAPSHOT_ID, or omit in LEGACY mode]", "snapshot": "[current SNAPSHOT_ID, or omit in MODE-OFF]"}
  7. 令牌优化的文件更新: 为了尽量减少 LLM 输出令牌,不要在输出中重新输出或手动重写整个 JSON 对象。 请改为在首次更新发现结果时,编写一个可复用的辅助脚本(例如 state_root/workspace/helpers/append_patch.py)。将版本标记注释作为辅助脚本的第一行: # MANTIS_HELPER_VERSION = 2。再次使用现有辅助脚本之前,先读取其第一行:如果其中不包含精确标记 MANTIS_HELPER_VERSION = 2,则说明辅助脚本已过时(来自不写入 patch_base_snapshot 的旧轮次/版本),应重新生成。只有标记匹配时才能复用:只需使用新参数执行现有辅助脚本,即可追加所需字段。

    必须将以下内容追加到现有对象:

    • 一个 "patch_status" 字段(取值为 "VERIFIED_SECURE"、 "MITIGATION_PROPOSED"、"VERIFICATION_INCOMPLETE"、 "VERIFICATION_FAILED" 或 "ERROR")。
    • 一个 "patch_base_snapshot" 字段,设为当前 SNAPSHOT_ID(Snapshot & Locator Resolution 步骤 0)——即获得此判定时所依据的快照。仅在 LEGACY 模式(不存在 SNAPSHOT_ID)下省略此字段。这样才能确保跨轮次的快照感知跳过(幂等性保证)和链式传播正确。
    • 如果补丁成功,则添加一个包含统一差异的 "patch_diff" 字段。
    • 如果执行了重新攻击,则添加 "reattack_status"、"reattack_file_path"、"reattack_run_command"、"reattack_output" 和 "reattack_variants" 字段("reattack_variants" 中的每个对象必须且只能包含 {"description": "...", "triggered": true/false})。
    • 向 "history" 数组添加一条记录:
    {
      "stage": "patch",
      "action": "patched",
       "details": "Patch status evaluated as [VERIFIED_SECURE/MITIGATION_PROPOSED/VERIFICATION_INCOMPLETE/VERIFICATION_FAILED/ERROR] on snapshot [patch_base_snapshot or 'legacy']",
      "pass_number": <current_pass_number>,
      "timestamp": "<current_iso8601_timestamp>"
    }
    

完成后,通知用户。

评论

支持 Markdown