关于 mantis-reproduce
mantis-reproduce 是面向安全工程师的漏洞验证技能,可在隔离环境中创建并运行 PoC 或载荷。它记录复现结果,并处理快照变更及补丁后的重新攻击验证。
主要功能
- 01创建 PoC 脚本或崩溃载荷
- 02在隔离环境中运行复现测试
- 03根据快照检查发现
- 04支持补丁后的重新攻击验证
- 05记录结果和复现产物
适用场景
- 01验证报告中的漏洞是否可触发
- 02在已打补丁的副本上重新检查发现
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ google/ mantis --skill mantis-reproduce安装后在 Claude Code 中输入/mantis-reproduce确认已加载。
详细介绍
原文在翻译后有更新,译文可能不是最新。
复现器(/mantis-reproduce)
系统目标
集成测试工程师。设计崩溃复现器或输入,并在隔离的沙箱环境中执行,以通过实证验证错误。
命令定义
- 命令:
/mantis-reproduce [--reattack] [--finding_id=<uuid>] [--force] [--target_root=<path>] [--state_root=<path>] [--snapshot_root=<path>] [--snapshot_id=<SNAPSHOT_ID>] [--snapshot_pinned=<true|false>] - 描述: 生成并运行崩溃复现器,以验证安全漏洞。
- 参数:
--reattack:作为补丁验证的一部分执行时,用于隔离重新攻击的结果。--finding_id:要复现的特定发现 UUID。必须提供;指定--reattack时此参数为必填项。--force:覆盖/绕过针对目标的常规运行资格检查。--target_root:待测目标代码库根目录的路径(默认为.)。提供时具有权威性——会覆盖--snapshot_root(区块 A 步骤 1a);对此目录会跳过哨兵检查(例如,在重新攻击验证期间使用已打补丁的影子副本)。--state_root:包含workspace/的 Mantis 状态目录根路径(默认为.)。--snapshot_root:本轮固定的不可变代码快照根目录。未提供--target_root时,由区块 A(步骤 0)使用。--snapshot_id:固定快照的 SNAPSHOT_ID 字符串,由步骤 0 中的区块 A(哨兵)和区块 B(快照匹配检查)使用。--snapshot_pinned:为false时(由mantis-patch在针对已打补丁影子副本重新攻击时设置),复现子 Agent MUST 在本次调用中跳过快照哨兵/匹配检查——--target_root目录具有权威性,不受哨兵检查约束(区块 A 步骤 1a)。
输入/输出约定
- 读取:
state_root/workspace/findings/(可行/有条件的发现)。target_root/(用于分析触发路径的仓库源文件)。state_root/workspace/archive/.repro_attempts.json。state_root/workspace/.mantis_state.json(用于跟踪当前循环轮次)。
- 写入:
- PoC 复现文件(例如
state_root/workspace/reproducers/中的poc_[uuid].py或crash_[uuid].payload)。 - 正常运行时:就地更新
state_root/workspace/findings/下的发现(设置"repro_status"、"repro_file_path"、"run_command"、"repro_output",并追加历史记录)。如果暂定为有效,则将状态更新为"VALID"。 - 使用
--reattack运行时:就地更新state_root/workspace/findings/下的发现(设置"reattack_status"、"reattack_file_path"、"reattack_run_command"、"reattack_output"、"reattack_variants",并追加阶段为"reattack"的历史记录)。不修改"repro_*"字段或"status"。**例外:**可根据步骤 6 中的 INV-1,以原子方式降低patch_status;不得同时持久化VERIFIED_SECURE和非failed_to_bypass的reattack_status。 - 以原子方式更新
state_root/workspace/archive/.repro_attempts.json。 - 在更新后的发现上标记
"repro_snapshot_id"/"reattack_snapshot_id",并将.repro_attempts.json的值存储为{count,last_snapshot}对象(仍可正确读取纯整数)。
- PoC 复现文件(例如
- 前置条件:
state_root/workspace/findings/中必须存在发现。- 必须具备沙箱/容器运行时环境。
- 幂等性保证:
- 就地更新发现。使用
state_root/workspace/archive/.repro_attempts.lock文件锁定,并通过原子临时文件替换(对state_root/workspace/archive/.repro_attempts.json.tmp调用os.replace),确保并发安全和重试稳定性。 - 感知快照:当发现的快照不再匹配时,重新生成 PoC;没有已到达 sink 的证据时,拒绝给出否定判定。快照不匹配时的 Re-attack 判定由 C5 未修补基线重跑(步骤 6)决定,该流程优先于旧版的一概拒绝规则——只有在 C5 确认当前快照下未修补基线仍会触发后,才会写入
failed_to_bypass判定。
- 就地更新发现。使用
说明
步骤 0:定位器解析 + 快照匹配(首先运行)
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 中的 harness 提供):**如果存在active_snapshot,且active_snapshot.pass != state.pass_number,则将该快照视为本轮的过期快照——停止并报告“stale active_snapshot: pass mismatch”,或降级为 HALT(snapshot_pinned实际为 false:不作权威判定,Block B 为 NOT_MATCHED,复现状态为not_attempted)。这可以捕获在第 15 阶段轮次递增后保留active_snapshot、却未重新固定快照的自定义 harness。参考 meta-agent 每轮都会重新固定快照,因此不会触发此检查。Block B 本身无法检测此情况(它只检查snapshot_id,不检查pass)。
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.)
注意:当 patcher 使用 --target_root=<shadow>(已修补副本)调用时,Block A 步骤 1a 会将该 shadow 设为权威 CODE_ROOT,并跳过哨兵检查(该 shadow 已被有意修改)。否则,CODE_ROOT 就是固定的快照,且哨兵必须匹配。对每个要更新的发现,将 repro_snapshot_id(正常运行)或 reattack_snapshot_id(--reattack)标记为当前 SNAPSHOT_ID(来自 --snapshot_id 或状态 active_snapshot.snapshot_id)。
编写概念验证复现脚本(Repro)或原始输入载荷文件,以复现已确认的安全缺陷。
在以下约束下执行复现阶段:
-
加载可行的发现:
- 如果提供了
--finding_id:- 仅加载该发现对应的文件
(
state_root/workspace/findings/<uuid>.json)。如果文件不存在,则退出。 - 如果指定了
--reattack:对已加载的发现强制执行预期的补丁工作流状态:- 该发现的
"status"必须为"VALID"或"PROVISIONALLY_VALID"。 - 该发现的
"repro_status"必须为"reproduced"。 - 该发现的
"patch_status"不得为"MITIGATION_PROPOSED"。 (允许"VERIFIED_SECURE":如果重新攻击的结果不是failed_to_bypass,下文 C5 会以原子方式将其降级。) - 如果不满足这些条件,则报错退出,并说明状态无效。
- 该发现的
- 如果未指定
--reattack(定向常规运行):- 如果未指定
--force,则执行标准资格筛选:- 该发现的
"status"必须为"VALID"或"PROVISIONALLY_VALID"。 - 该发现的
"production_viability"必须为"VIABLE"、"SAMPLE_OR_TEST"或"CONDITIONAL_VIABLE"。 - 如果不满足这些条件,则报错退出,并说明状态无效。
- 该发现的
- 如果指定了
--force,则跳过这些资格检查。
- 如果未指定
- 仅加载该发现对应的文件
(
- 如果未提供
--finding_id:- **约束:**如果指定了
--reattack,则退出(该选项需要--finding_id)。 - 读取
state_root/workspace/findings/目录中的 JSON 文件。 - **严格资格筛选(常规运行):**仅纳入符合以下条件的发现:
"status"为"VALID"或"PROVISIONALLY_VALID"。"production_viability"为"VIABLE"、"SAMPLE_OR_TEST"或"CONDITIONAL_VIABLE"(如果不检查可行性,可跳过此可行性筛选,但始终检查状态)。
- 如果不存在适用的发现,则通知用户并退出。
- **约束:**如果指定了
第 0 层 — 结构可达性预检查(建议队列排序):如果结构代码索引(
mantis-structural-index)可用,你可以在编写代码前查询query_structural_index.py(find_callers),检查从公共入口点到漏洞接收点是否存在 AST 调用路径。使用此查询对候选执行顺序进行优先级排序(先处理已验证存在 AST 可达路径的发现)。- **重要提示——仅供参考的防护规则:**AST 可达性仅是排序提示。调用图会遗漏宏、函数指针、动态分派和接口表。绝不能因缺少调用路径而拒绝某项发现、跳过复现或设置
failed_to_reproduce。
**快照漂移检查:**对于每个已加载的发现,如果已有
repro_snapshot_id,且区块 B(步骤 0)返回 NOT_MATCHED,则将任何已存储的 PoC/偏移量视为过期:针对当前 CODE_ROOT 从头重新生成复现程序(不要复用旧行号/地址)。如果区块 B 为 MATCHED,则可以复用现有 PoC。 - 如果提供了
-
严格的主机隔离约束:
- 严禁执行主机命令。不得使用终端/Shell 执行工具,直接在父级主机终端运行命令。
- 所有复现程序都必须在隔离环境中运行。使用环境提供的容器化或沙箱执行工具。对于内存安全 PoC,应尽可能限制网络访问和文件系统写入。对于逻辑/身份验证功能测试,可按需启用本地网络服务,但绝不能让环境暴露于外部互联网。
-
编写并运行复现程序: 编写一个自包含的测试脚本(例如
poc_[uuid].py或同一目录中的 C 复现程序文件),或编写一个可触发目标漏洞的原始崩溃输入数据载荷(例如crash_[uuid].payload)。所有生成的 PoC/重新攻击脚本和载荷都必须写入state_root/workspace/reproducers/目录(绝不能写入target_root目录)。 写入任何文件之前,必须确保父目录state_root/workspace/reproducers/已存在(例如,使用mkdir -p)。仔细分析代码路径和约束。如果首次复现尝试失败,请根据观察结果评估发现详情(如输入路径、参数或假设)是否存在细微错误,并动态调整发现详情以尝试修复。如果尝试多种方法并做出调整后仍找不到可触发的路径,请放弃尝试。不要直接将其标记为failed_to_reproduce——应通过 Step-5 Block F(到达漏洞汇点的证据)门控来决定是否放弃:如果有证据证明测试框架已到达易受攻击的入口点,但漏洞未触发,则归类为failed_to_reproduce;如果没有证据(设置/构建失败、退出码为 127、“No such file” 或从未到达漏洞汇点),则归类为not_attempted(可重试)。设置/构建失败时的原始负面结果会耗尽重试上限,并悄无声息地漏掉真实漏洞。
要运行脚本或载荷,请使用环境中可用的执行或容器化工具安全地执行代码。为目标选择最合适的运行时镜像和标志。所有编译和测试执行命令都必须在 PRIVATE BUILD SHADOW 中运行,绝不能使用 Cwd=CODE_ROOT(快照为只读 —— Block A 第 4 步)。 编译前,创建 BUILD_ROOT=$(mktemp -d) 并将 CODE_ROOT 复制到其中(例如 cp -a CODE_ROOT/. BUILD_ROOT/);使用 Cwd=BUILD_ROOT 运行所有编译/测试命令;拆除时删除 BUILD_ROOT。生成的 PoC 文件本身必须保存在 state_root/workspace/reproducers/ 下(相对于 STATE 的路径),并将其绝对路径存入 "run_command"/"reattack_run_command"。
消毒器编译(C/C++ 目标): 如果漏洞类别属于内存安全或未定义行为,至少使用 -fsanitize=address,undefined(ASan + UBSan)进行编译。对于数据竞争,添加 -fsanitize=thread(TSan)——TSan 也不能与 ASan 同时使用,因此应单独构建。使用 -fno-omit-frame-pointer 以获得可用的堆栈跟踪。这些标志可以揭示原本会静默地以 0 退出的漏洞(UBSan 默认采用恢复模式)。MSan 注意事项: MemorySanitizer(-fsanitize=memory)可检测对未初始化内存的读取,但它要求整个依赖链(包括 libc 和 C++ 运行时)都经过 MSan 插桩——对于任意目标,通常无法满足这一要求,因此简单地使用 -fsanitize=memory 构建会产生虚假的 WARNING: MemorySanitizer 跟踪信息。MSan 也不能与 ASan 在同一次构建中同时使用(应单独构建,而非叠加标志)。如果无法保证环境已进行完整插桩,不要添加 -fsanitize=memory;应改用 ASan+UBSan,而不是相信可能虚假的 MSan 跟踪信息。如果存在经过合法 MSan 插桩的构建环境,将 MSan 输出识别为证据(Step 5)仍然是正确的。**消毒器一致性保护措施:**基线、攻击和重新攻击运行中必须使用相同的消毒器标志——使用不同的消毒器可能会掩盖漏洞并导致假阴性(INV-2)或错误的 VERIFIED_SECURE(INV-1)。参见 Block G。
{TARGET_ROOT} 标记替换(编号步骤):
- 编写
run_command或reattack_run_command时,凡是引用目标树路径,都使用字面标记{TARGET_ROOT}。 - 运行时,reproduce 会将
{TARGET_ROOT}替换为实际根目录:- 首次执行时:
CODE_ROOT(快照或--target_root)。 - 重新执行时(重新攻击或重试):当前的
CODE_ROOT/--target_root/active_snapshot.root。
- 首次执行时:
- 这样可确保快照 GC 后存储的命令仍能正确解析——绝不要将绝对
.mantis_snapshots/pass_<N>路径写入存储的命令。
使用适当的环境执行复现: 如果目标是固件,可以编写脚本,通过 qemu、unicorn
或 Firmadyne 启动它。如果目标是二进制文件,可以使用动态插桩或标准执行方式。请自行判断,为
该制品构建可用的测试框架。
-
并行轨迹搜索与分层迭代复现:
-
并行轨迹搜索(广度优先): 如果有子 Agent 可用,应部署并发工作器,采用不同的逻辑思路复现漏洞。如果任何一条轨迹成功,立即采用其有效载荷并舍弃其他轨迹,以避免陷入潜在的“放弃”循环并减少计算开销。
-
分层迭代复现(深度优先的有效载荷优化): 每条轨迹工作器(或单个 Agent)都应使用分层升级阶梯(Tier 1 -> Tier 2 -> Tier 3),逐步优化触发有效载荷,而不是一次性尝试端到端启动。
-
-
分层迭代执行阶梯:
- Tier 1(微型测试框架 / 接收点逻辑验证): 构建轻量级测试,直接调用存在漏洞的函数或模块,以验证核心漏洞假设在隔离环境中成立。
- Tier 2(子系统 / 接口验证): 让有效载荷经过输入序列化、解析器、路由和身份验证封装,确认输入能在中间处理过程中保留下来,且不会被清理或截断。
- Tier 3(完整沙盒服务 / 端到端验证): 在隔离沙盒(Docker、QEMU、VM)内,通过公共 API 对目标服务执行自包含 PoC。根据 Block F,只有此步骤才能得出权威的
reproduced判定。
-
关键的 Step 4 Tier 1 硬性门槛(失败即拒绝):
- Tier 1 和 Tier 2 仅是内部过渡步骤。绝不能仅依据 Tier 1 或 Tier 2 的执行结果,将
repro_status记录为"reproduced"或"statically_confirmed"。 - 如果只有通过编译直接调用测试框架来调用私有或静态函数,或绕过公共 API(Step 4),才能触发崩溃,并且无法将有效载荷升级到通过 Tier 3(公共 API / 沙盒服务)触发,则必须终止并记录
repro_status = "failed_to_reproduce",并在详情中注明"Internal Invariant Protection"。
- Tier 1 和 Tier 2 仅是内部过渡步骤。绝不能仅依据 Tier 1 或 Tier 2 的执行结果,将
-
尝试次数上限计算与本地重试:
- Tier 3 以下的过渡步骤子预算: Tier 1 和 Tier 2 的内部试运行属于有界本地执行步骤(每次对话最多 3 次试运行),且不会增加
state_root/workspace/archive/.repro_attempts.json中每个发现问题的绝对尝试次数计数器。 - 绝对尝试次数上限计数: 只有 Tier 3 的完整沙盒服务执行(或完整端到端复现运行)才会增加绝对尝试次数计数器,并计入上限 6 次(第 6 节)。
- 对话内重试: 某个 Tier 失败时,请检查日志、调整有效载荷参数、修复测试框架设置,并在向协调器报告前,于当前对话中重试 2–3 次。
- Tier 3 以下的过渡步骤子预算: Tier 1 和 Tier 2 的内部试运行属于有界本地执行步骤(每次对话最多 3 次试运行),且不会增加
Step 3a:变体搜寻(仅限重新攻击,必需)
使用 --reattack 调用时,除了重新运行原始 PoC 外,MUST 编写并执行 N ≥ 3
个边界变异的输入。
schema.json 将此明确称为“变体搜寻式重新攻击”
——仅重新运行原始 PoC 并不足够。过度狭窄、只防护 PoC 精确字节内容的补丁
是自动修复失败的主要模式;搜寻变体是零基础设施成本的防御手段。
历史发现项: 对于缺少 reattack_variants 的发现项(创建时本规则尚不存在),首次执行
--reattack 时会按 ≥3 个变体的要求重新验证。这是有意为之——历史的
VERIFIED_SECURE 判定将升级为更严格的门槛——但首次执行变体搜寻时,预计会对归档发现项
产生一次性变动。
需要生成的内容(按漏洞类别区分):
-
内存安全漏洞(缓冲区溢出、越界读/写、UAF、整数 溢出): 至少编写以下 3 种变体:
- 差一错误:
len = bound、len = bound + 1、len = bound - 1。 - 大小变异:
len ± 1、len = 0、len = SIZE_MAX、对有符号长度进行符号翻转。 - 通往同一汇点的替代路径: 如果可通过多个调用点到达存在漏洞的汇点,则编写一个通过不同路径到达该汇点的变体(例如,不同的 API 端点、文件格式变体或协议命令)。
- 类型混淆/宽度不匹配: 利用不同的类型路径到达同一汇点。
- 差一错误:
-
非内存安全漏洞(逻辑、身份验证、注入、SSRF、路径遍历): 搜寻同一类别的实际变体:
- 替代端点/参数: 如果 PoC 针对的是
/api/v1/echo,则尝试/api/v2/echo,或替代参数名。 - 等效载荷:
..%2fetc%2fpasswd、..\\..\\、URL 编码、双重编码、Unicode 规范化变体。 - 身份验证边界变体: 不同角色、空/null 令牌、替代的权限提升路径。
- 注入变体:
'; EXEC--、UNION SELECT、盲注变体、替代注入点。
- 替代端点/参数: 如果 PoC 针对的是
-
并行生成变体(如果有可用的子 Agent): SHOULD 启动子 Agent 并行编写和测试变体。每个子 Agent 负责一种变异策略,编写其变体 PoC,并报告是否触发。设置
reattack_status前,汇总所有结果。如果没有可用的子 Agent,则按顺序逐一处理。
执行: 将每个变体写成独立脚本,放入
state_root/workspace/reproducers/(例如,reattack_variant_[uuid]_[N].py)。
按照步骤 3 的相同隔离约束,使用 --target_root 对修补后的影子环境执行每个变体。按照步骤 6,将每个变体记录在 reattack_variants 数组中,键必须严格符合以下格式:
{"description": "...", "triggered": true/false}。
判定规则: 只有当 reattack_variants 数组非空、包含 ≥ 3 个有效变体输入,并且这些变体在修补后的影子环境中全部未能触发漏洞时,reattack_status = "failed_to_bypass" 才成立。数组为空或数量不足,会导致“所有变体均未触发”在逻辑上空泛成立——这是禁止的:如果经过实质性尝试后仍无法构造出至少 3 个有意义的变体,则将状态上限设为 VERIFICATION_INCOMPLETE(历史记录说明 insufficient_variants),绝不能设为 failed_to_bypass。[] 情况只能与非 failed_to_bypass 状态共存(例如,C5 基线失败导致变体搜寻提前停止)。
变体有效性保护规则: 只有同时满足以下两项条件,变体才能算作成功绕过补丁(triggered = true 导致 bypassed_patch):
- 相同的漏洞类别: 变体复现了原始漏洞类别——
相同的危险函数/净化器签名/崩溃类型。无效变体(例如,
len=SIZE_MAX导致无关的内存不足错误、替代端点返回 404 后报错,或完全不同的崩溃) 不算绕过。丢弃该变体(将triggered = false,并在描述中注明该变体无效), 然后继续。对于非内存漏洞(逻辑、身份验证、注入),应比较相同的 危险函数或行为(例如,相同的未授权操作成功、相同的注入执行,或到达相同的 危险函数)——而不是与补丁前的行号比较 ± 若干行(补丁会导致行号偏移, 且非内存漏洞通常没有精确的危险行)。如果无法确切确认某个触发变体是不同的 漏洞,则将其计为绕过(失败时采用封闭策略: 宁可错误地判定为 VERIFICATION_FAILED,也不要错误地判定为 VERIFIED_SECURE)。 - 符合步骤 4 的有效输入: 该变体必须是对公共 API 或内部不变量的有效测试—— 不得依赖人为构造的测试框架技巧(例如,直接调用私有函数并使用自定义分配的缓冲区), 以此绕过库的执行不变量。
理想情况下,应确认每个触发变体在未打补丁的影子版本上仍会触发(即原始 PoC 运行时所使用的同一基线),以证明它测试的是原始漏洞,而非其他因素造成的结果。 如果未打补丁的影子版本不可用(例如,快照不匹配),则应要求提供相同危险点的证据 (相同的净化器栈帧、相同的崩溃地址、相同的逻辑故障)作为佐证。
如果任何有效变体触发了漏洞,则将 reattack_status = "bypassed_patch",
并应用 INV-1(将 VERIFIED_SECURE 降级为 VERIFICATION_FAILED)。
-
复现状态分类:
reproduced:PoC 成功触发了漏洞。failed_to_reproduce:已执行 PoC,但未触发漏洞。statically_confirmed:由于环境限制(例如,缺少硬件模拟器、无法使用外部 服务)而无法复现,但缺陷在静态分析中显而易见(例如,凭据硬编码)。 强烈不建议使用此状态,仅应作为最后手段。not_attempted:完全跳过了复现阶段(例如,由于基础设施设置失败、超时, 或显式跳过配置)。
-
严格遵守公共 API 和内部不变量约束:
- 崩溃复现程序应尽可能通过面向公众的 API 与代码库交互,或严格遵守库的全局 执行不变量(例如分配器填充),以避免产生人为且不具可行性的崩溃。
- 如果只有通过编译直接调用测试框架,向私有/静态函数传入自定义分配的缓冲区
(例如
malloc(15)),并绕过库保证使用的分配器封装(例如png_malloc(rowbytes + 48)),才能导致崩溃,则不得将该发现声明为“已复现”。 - 如果无法通过公共 API 或使用标准分配填充触发崩溃,则应将该发现归类为
"failed_to_reproduce",原因是“内部不变量保护”。
-
功能与崩溃感知验证: 根据漏洞类别,分析 stdout、stderr 和退出代码等输出, 以判定复现是否成功:
在归类任何负面结果(
failed_to_reproduce,或在--reattack模式下的failed_to_bypass)之前,先执行以下检查:REACHED-SINK EVIDENCE GATE (mechanical): Each reproducer produces REACHED-SINK EVIDENCE via ONE channel, recorded in repro_hints: (a) script/source harness -> write the exact bytes MANTIS_REACHED_ENTRYPOINT to a sidecar file $SENTINEL_FILE and flush+fsync (or unbuffered write) BEFORE invoking the sink. (A file survives a crash that truncates buffered stdout.) (b) binary / firmware / raw-payload -> reached-sink evidence is a captured crash backtrace or sanitizer frame (ASan/UBSan/MSan/TSan) that explicitly names the target sink function (target-produced tracing). A marker written by a wrapper you author BEFORE invoking the target is SETUP EVIDENCE ONLY: it proves "launch attempted," not "sink reached," and does NOT qualify as reached-sink evidence. If no in-path marker (channel a) and no target-produced backtrace/sanitizer trace (channel b) is achievable, the sink is unreached. EVIDENCE PRESENT (reached-sink) = (channel a) sidecar file contains MANTIS_REACHED_ENTRYPOINT written in-path, OR (channel b) target-produced backtrace/sanitizer output names the sink. A wrapper pre-launch marker alone is NOT evidence present. EVIDENCE ABSENT includes: any compiler/build nonzero exit; exit 127 (command not found); exit 2 with a "No such file" message. DECISION GATE (gate the DECISION, not specific verdict strings): - Record repro_status = reproduced OR statically_confirmed ONLY if EVIDENCE is PRESENT. If ABSENT -> repro_status = not_attempted (retry-eligible), STOP. - In patch verification, EVIDENCE is required on the UNPATCHED baseline (Block G), NOT on the post-patch attack run (a correct patch legitimately stops the input before the sink). - If NO evidence channel is achievable for this target, downgrade to not_attempted / VERIFICATION_INCOMPLETE. NEVER synthesize the marker.具体来说:如果本次运行没有产生任何到达漏洞触发点的证据(构建/设置错误、退出码为 127、“No such file”,或从未到达漏洞触发点),则记录
repro_status = not_attempted(可重试)——绝不要记录failed_to_reproduce;在--reattack模式下,将reattack_status留空 并在历史记录中注明“setup_failed”——绝不要记录failed_to_bypass。只有在能够证明测试框架已到达易受攻击的入口点,且漏洞未触发时,才能将结果判定为否定。HALT 上限(3 态规则): 如果状态中存在
active_snapshot,但snapshot_pinned为false(HALT 模式——代码树发生变化或无法固定),则绝不能记录failed_to_reproduce或failed_to_bypass。在 HALT 模式下,代码可能已发生变化,因此不能将否定的复现结果视为权威结论。应改为记录repro_status = not_attempted(可重试);在--reattack模式下,将reattack_status留空,并在历史记录中注明“HALT mode: snapshot unpinned, negative result suppressed”。这与适用于 HALT 模式下所有阶段的权威判定禁令一致。(在 MODE-OFF 模式下——即没有active_snapshot——照常判定否定结果。)- 逻辑与授权漏洞: 成功的复现程序是一个功能测试或脚本,能够明确证明逻辑故障(例如,未授权请求返回
200 OK,或测试脚本成功绕过验证并以0退出)。 - 内存安全与二进制崩溃: 如果沙箱执行在 stdout/stderr 中产生崩溃信号或消毒器跟踪信息,则将复现标记为
"reproduced"。无论退出码如何,都要扫描 stdout/stderr 中的消毒器特征——UBSan 默认使用恢复模式(退出码为 0),因此退出码为 0 的运行仍可能包含有效的 UBSan 跟踪信息,证明漏洞已触发。请检查:- AddressSanitizer (ASan) 错误输出(例如
ERROR: AddressSanitizer)。 - UndefinedBehaviorSanitizer (UBSan) 运行时报告(例如
runtime error:,SUMMARY: UndefinedBehaviorSanitizer)。 - MemorySanitizer (MSan) 错误输出(例如
WARNING: MemorySanitizer)。 只有在repro_hints记录了可用的全插桩 MSan 构建时,MSan 证据才有效(步骤 3 的注意事项)。未进行完整插桩的简单-fsanitize=memory构建会产生虚假跟踪信息——不要将这些结果判定为"reproduced"。 - ThreadSanitizer (TSan) 竞态报告(例如
WARNING: ThreadSanitizer)。 - 分段错误(SIGSEGV,退出码
139)。 - 中止信号(SIGABRT,退出码
134)。 - 崩溃转储或核心转储。
- AddressSanitizer (ASan) 错误输出(例如
- 逻辑与授权漏洞: 成功的复现程序是一个功能测试或脚本,能够明确证明逻辑故障(例如,未授权请求返回
-
令牌优化的文件更新: 为尽量减少 LLM 输出令牌,不要在输出中重新输出或手动重写整个 JSON 对象。 请使用原地编辑工具(例如,用你偏好的语言编写简短脚本,或使用
jq),以编程方式将新字段追加到现有的state_root/workspace/findings/<id>.json文件中。此外,你必须更新复现尝试缓存,以帮助规划器高效跟踪尝试:
- 在
state_root/workspace/archive/.repro_attempts.json维护一个 JSON 缓存文件。创建、读取或锁定缓存文件之前,确保其父目录state_root/workspace/archive/已存在(例如,mkdir -p state_root/workspace/archive/)。 - 使用一个稳定标识作为缓存键,该标识在循环运行之间保持不变,即使 UUID 重新生成也不受影响。如果发现项有
signature字段,直接将其用作缓存键(它已经是确定性的内容标识哈希)。如果没有signature,则使用发现项的规范化标题和主要文件路径计算一个稳定键:stable_key = normalized_title + "@" + primary_file_path。- 通过将标题转换为小写并移除所有非字母数字字符来计算
normalized_title。 - 通过取
code_paths中的第一个条目并剥除任何行号后缀来计算primary_file_path(例如,将src/auth.c:120转换为src/auth.c)。 - 快照感知上限(值的形状为
{count, last_snapshot}): 将每个缓存值存储为对象{count, last_snapshot}。读取值 V 时:如果 V 是一个纯整数,则将 count 视为 V,last_snapshot 视为 UNKNOWN;如果 V 是一个对象,则使用 V.count / V.last_snapshot(默认为 UNKNOWN)。递增之前,运行 Block B,将发现项的快照与当前 SNAPSHOT_ID 进行比较:仅当 Block B 为 NOT_MATCHED,且原因是两个快照都存在并且确实不同(真实代码变更可获得新的预算)时,才将count=0。在 UNKNOWN(缺失/pass_/未固定)时不要重置——否则没有 VCS 的目标会无限重试。此外,为每个发现项 ID 维护一个绝对尝试计数器,该计数器绝不重置,并在达到硬上限(例如 6)后停止重试,无论快照是否发生变化。
- 通过将标题转换为小写并移除所有非字母数字字符来计算
- 为防止并发执行时出现竞态条件(包括原子文件替换导致的绕过锁定),并保护无锁读取者:
- 使用单独的专用锁文件
state_root/workspace/archive/.repro_attempts.lock,该文件绝不删除或替换。 - 使用 Python 的
fcntl.flock对此锁文件执行原子更新:
-
打开锁文件
state_root/workspace/archive/.repro_attempts.lock(如果不存在则创建),并在上下文管理器(with语句)中获取排他锁(使用fcntl.flock和fcntl.LOCK_EX)。 -
读取缓存文件
state_root/workspace/archive/.repro_attempts.json的当前内容(如果文件不存在或为空,则视为{})。 -
仅对 Tier-3(完整的端到端沙盒化服务)执行,将该发现项缓存键条目的
count字段(使用signature(若存在),否则使用stable_key;与上文定义的键选择方式相同)加 1(该条目为{count, last_snapshot}对象)。内部 Tier-1 和 Tier-2 过渡试验不得递增count(它们受 Step 3 中的子预算规则约束)。 -
将更新后的 JSON 写入同一目录下的临时文件(例如,
state_root/workspace/archive/.repro_attempts.json.tmp)。 -
使用原子操作将临时文件替换为目标缓存文件(例如,在 Python 中使用
os.replace),确保读取者永远不会看到被截断或不完整的文件。 -
关闭锁文件描述符以释放锁(退出
with上下文管理器时会自动处理)。
- 使用单独的专用锁文件
根据是否提供
--reattack标志:-
如果正常运行(不带
--reattack标志): 必须在现有对象上追加或更新以下内容:-
"repro_status"("reproduced"、"statically_confirmed"、"not_attempted"或"failed_to_reproduce")。 -
"repro_file_path" -
"run_command" -
"repro_output" -
"repro_snapshot_id":本次运行所针对的当前 SNAPSHOT_ID。 -
"repro_hints":记录编译和沙箱执行遥测数据 (例如,sanitizers_used: ASan+UBSan、assertions_disabled: true、build_profile: release),为/mantis-critic提供实证执行依据。 -
如果复现成功(
repro_status被判定为"reproduced"或"statically_confirmed"),且发现当前"status"为"PROVISIONALLY_VALID":升级前,检查该发现的triage_checklist(如果存在)。如果任何条目的outcome == "UNKNOWN"(或passes == false),则不得升级:保持status为"PROVISIONALLY_VALID",仍将repro_status设为成功值(复现确实成功), 并追加一条历史记录upgrade-to-VALID-blocked: triage_checklist has UNKNOWN entries (re-review required)。 这样可以避免违反架构的VALID ⇒ no UNKNOWNallOf 门控条件 (schema.json 第 471-507 行),该条件禁止任何VALID非链式发现的triage_checklist中出现UNKNOWN/passes:false。复现不会修改triage_checklist条目(该清单属于 review 阶段的产物;只有 review 才能解决UNKNOWN条目)。如果triage_checklist不存在(没有reviewer历史条目,例如遗留发现),或没有条目为UNKNOWN/passes:false, 则必须将"status"更新为"VALID"。 -
向
"history"数组添加一条记录:
{ "stage": "reproduce", "action": "reproduced", "details": "Reproduction status evaluated as [reproduced/failed_to_reproduce] using command: [run_command]", "pass_number": <current_pass_number>, "timestamp": "<current_iso8601_timestamp>" } -
-
如果使用
--reattack运行: 必须在现有对象上追加或更新以下字段 (不要修改repro_*或status):-
"reattack_status"("bypassed_patch"、"failed_to_bypass"、"inconclusive_baseline_changed")。 -
"bypassed_patch":PoC 绕过了补丁并触发了漏洞。如果patch_status为VERIFIED_SECURE,则在同一次写入中原子地将patch_status = "VERIFICATION_FAILED"(绕过补丁即证明补丁失效)。这是对 “不要修改 status”规则的明确例外,适用于 C5 步骤 3 路径和同快照运行。 -
"failed_to_bypass":运行了 PoC,但未能绕过补丁。(无需降级——failed_to_bypass是 allOf 门控对VERIFIED_SECURE所要求的值。) -
"inconclusive_baseline_changed":重新运行了未打补丁的基线(见下文 C5),且当前未打补丁的代码已不再触发漏洞。不得声称failed_to_bypass。如果patch_status为VERIFIED_SECURE,则在同一次 写入中原子地将patch_status = "VERIFICATION_INCOMPLETE"。 -
INV-1(单一依据——架构的
VERIFIED_SECUREallOf 门控条件):VERIFIED_SECURE => reattack_status必须为failed_to_bypass,而failed_to_bypass要求reattack_variants数组非空,且包含至少 3 个有效 变体输入(步骤 3a),这些输入在打补丁的影子环境中全部未能触发漏洞。 变体集为空或数量不足会导致“全部失败”在逻辑上空真——这是禁止的:此时 必须将状态限制为VERIFICATION_INCOMPLETE。任何其他结果都必须在同一次写入中 原子地降级patch_status——对于inconclusive_baseline_changed或变体数量 不足,设为VERIFICATION_INCOMPLETE;对于bypassed_patch,设为VERIFICATION_FAILED。绝不可同时保留VERIFIED_SECURE和非failed_to_bypass的reattack_status。 -
快照不匹配 -> 按照下方 C5 处理: 当
reattack_snapshot_id!=repro_snapshot_id时,不要独立 记录判定——遵循 C5,在当前快照上重新确立未打补丁基线。如果无法 运行 C5,则将reattack_status留为 UNSET,并添加SNAPSHOT_MISMATCH历史备注。 -
C5 — 重新运行未打补丁基线(阶段 2): 在针对已打补丁构建版本运行攻击 之前,如果
reattack_snapshot_id!=repro_snapshot_id(确实发生了快照变更, 而非 HALT 模式下的live:树),则首先重新确立未打补丁基线:- 针对当前快照的新鲜未打补丁副本运行复现程序——使用
mktemp -d创建active_snapshot.root的全新副本,而不是已打补丁的影子副本。(这与mantis-patchBlock G 第 1 步运行的未打补丁基线相同;reproduce 没有 Block G。) - 如果未打补丁基线未触发(根据 Block F,缺少证据):设置
reattack_status = "inconclusive_baseline_changed",不要继续运行攻击, 不要声称failed_to_bypass。仍必须填充reattack_file_path、reattack_run_command和reattack_output,记录基线重新运行的详情。应用 INV-1 (将VERIFIED_SECURE降级为VERIFICATION_INCOMPLETE)。 - 如果未打补丁基线触发:继续运行攻击
(
bypassed_patch或failed_to_bypass)。应用 INV-1(若为bypassed_patch, 则将VERIFIED_SECURE降级为VERIFICATION_FAILED)。
- Sanitizer 一致性: 基线重新运行、攻击和重新攻击必须使用触发原始基线时
相同的 sanitizer 标志。如果基线通过 UBSan 触发(退出代码为 0),在不启用
UBSan 的情况下重新编译攻击将掩盖 bug,并导致误报阴性(INV-2)或错误的
failed_to_bypass(INV-1)。 - HALT 模式防护: 通过状态中的 PASS 快照 ID 检测 HALT
(
active_snapshot.snapshot_id以live:开头),而不是通过--snapshot_pinned=false参数(该参数仅用于豁免已打补丁影子副本的哨兵检查)。 在 HALT 模式下,跳过 C5,并遵循现有的 HALT 上限要求 (将reattack_status留为 UNSET,并添加 HALT 备注)。 - 这不会改变 VERIFIED_SECURE 门槛——Block G 仍要求未打补丁基线触发。
- 针对当前快照的新鲜未打补丁副本运行复现程序——使用
-
"reattack_file_path" -
"reattack_run_command" -
"reattack_output" -
"reattack_variants":一个对象数组,每个对象对应在步骤 3a 中尝试的一个变体 输入。每个对象必须且只能包含两个必需键:"description"(字符串)和"triggered"(布尔值)。不要编造自由格式或自定义键(例如input、result、bypassed、label、name):"reattack_variants": [ { "description": "差一错误:len=bound+1", "triggered": false }, { "description": "通过 /api/v2/echo 的备用路径", "triggered": false }, { "description": "边界变异:最大值 INT_MAX", "triggered": false } ]"description":该变体做什么(例如"off-by-one: len=bound+1"、"alternate path via /api/v2/echo")。"triggered":布尔值 — 该变体是否在修补后的 shadow 上触发了原始 漏洞类别(依据 Step 3a 中的变体有效性防护规则)。产生了无关 崩溃或错误的无效/垃圾变异体记录为triggered = false,并在描述中 注明其无效性。对于reattack_status = "failed_to_bypass",reattack_variants必须非空(≥ 3 项)。空数组[]只能 与非failed_to_bypass状态共存(例如,在变体搜寻之前 就中止的 C5 基线失败,或inconclusive_baseline_changed)。
-
"reattack_snapshot_id":本次运行所针对执行的当前 SNAPSHOT_ID。 -
添加到
"history"数组的一项:
{ "stage": "reattack", "action": "reproduced", "details": "Re-attack status evaluated as [bypassed_patch/failed_to_bypass] using command: [reattack_run_command]", "pass_number": <current_pass_number>, "timestamp": "<current_iso8601_timestamp>" } -
- 在
-
对复现有效性的批判: 为确保复现是所报告漏洞的有效 复现示例,让一个拥有全新上下文窗口的子代理审查并批判 生成的 PoC。寻求真正的批判,以确保虚假报告日后 绝不会被呈报出来。
变体批判(仅限 re-attack): 当指定
--reattack时, 批判子代理还必须验证变体输入(Step 3a)确实具有 多样性 — 而非微不足道的相同变异(例如,更改注释而保留相同 载荷)。如果批判者发现这些变体没有实质性的多样性,记录一条 history 备注variant_diversity_insufficient,并在最终确定reattack_status之前重新编写变体。
完成后,通知用户。