关于 mantis-chain
这项 Mantis 安全 Skill 会分析已验证的漏洞发现和知识库条目,寻找多步利用链。在固定快照的运行中,只使用与当前代码快照匹配的发现。
主要功能
- 01分析已验证的漏洞发现
- 02按前置条件与效果关联发现
- 03将知识库原语用作链路
- 04按快照匹配筛选组成项
- 05避免跨运行重复生成链
适用场景
- 01评估多个漏洞组合后的影响
- 02利用架构原语寻找攻击路径
- 03避免过期发现进入固定快照利用链
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ google/ mantis --skill mantis-chain安装后在 Claude Code 中输入/mantis-chain确认已加载。
详细介绍
漏洞链构建器(/mantis-chain)
系统目标
漏洞利用链架构师。分析彼此隔离且已单独验证的安全发现,以及历史知识库中的原语,以识别并构建复杂的多步骤漏洞利用链。
命令定义
- 命令:
/mantis-chain - 描述: 分析单个安全发现,以识别并构建复杂的漏洞利用链。
- 参数(均为可选;由编排器根据 harness 生命周期契约提供——参见
schema.json中的 “Non-JSON Contracts”:--snapshot_root/SNAPSHOT_ROOT— 本轮使用的固定、不可变代码快照根目录。由 Block A 使用。--snapshot_id/SNAPSHOT_ID— 本轮的快照身份字符串。由 Block A(哨兵)和 Block B(匹配检查)使用。--state_root—workspace/的父目录。若未提供,默认为当前目录(Block A 步骤 1c/1d)。--target_root— 已准备好的树,会覆盖快照根目录(此阶段很少使用;由 Block A 步骤 1a 读取)。如果这些参数均未提供,Block A 会回退到workspace/.mantis_state.json中的active_snapshot;如果该值也不存在,则阶段将以 MODE-OFF(snapshot_pinned = false)运行,行为与当前完全一致(参见向后兼容性)。
输入/输出约定
- 读取:
workspace/findings/(已验证的发现 JSON 文件,其中状态为"VALID",且可行性为"VIABLE"、"CONDITIONAL_VIABLE"或"SAMPLE_OR_TEST")。workspace/kb/entities/*.md和workspace/kb/vulnerabilities/*.md(知识库基础条目)。workspace/.mantis_state.json(用于跟踪当前循环轮次)。
- 写入:
- 将全新的漏洞利用链发现 JSON 文件写入
workspace/findings/<new_uuid>.json。原始发现保持不变。
- 将全新的漏洞利用链发现 JSON 文件写入
- 前置条件:
workspace/findings/中必须存在已验证或可行的发现。
- 幂等性保证:
- 写入新的漏洞利用链发现之前,技能必须通过比较组成该链的发现序列,检查现有的漏洞利用链发现。扫描当前的
workspace/findings/目录,以及workspace/archive/findings_pass_*/下的每个归档轮次和旧版workspace/archive/loop*_findings/(均相对于状态路径 — 绝不能添加CODE_ROOT前缀)。任何包含非空constituent_findings数组的发现 JSON 都属于链。如果任一位置已存在组成发现顺序完全相同的链,则跳过创建重复项。 - 按签名键控的幂等性(首选)——以
code_paths作为平局判定条件:如果所有组成发现都有signature字段,则比较按顺序排列的组成发现signature序列(而不是 UUID)。仅当两条链的有序signature序列相等,并且每一对对应的组成发现都至少有一个相同的code_paths条目(包含行号,即比较时包含末尾的:line)时,才将两条链视为相同(并跳过创建新链)。如果签名序列相同,但任一对应组成发现的code_paths不同,则两条链不相同 — 创建新链。(多报是安全的;绝不能因为signature冲突而抑制真正的新漏洞利用链,因为signature会去除行号,可能导致同一文件中的不同错误发生冲突。) - **UUID 回退(当前行为):**如果任一组成发现缺少
signature字段,则像以前一样,回退到精确比较按顺序排列的组成发现 UUID 序列。这可以保留旧版/未升级发现的当前行为。 - 稳定的发现签名现已使此归档扫描完全生效:组成发现签名相同的两条链会在不同轮次间正确去重。缺少签名时(旧版发现),扫描仍然安全无害 — 它不会错误抑制合法的新链(不会悄无声息地丢弃结果),只会在恢复/重新运行的轮次中阻止 UUID 完全相同的重复项。如果
workspace/archive/目录不存在,则将归档集合视为空并继续。
- 写入新的漏洞利用链发现之前,技能必须通过比较组成该链的发现序列,检查现有的漏洞利用链发现。扫描当前的
说明
定位解析(首先运行——所有阶段)
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)。这可以捕获在 Stage 15 递增轮次后保留了active_snapshot、却未重新固定快照的自定义 harness。参考 meta-agent 每轮都会重新固定快照,因此不会触发此检查。Block B 本身无法检测此情况(它只检查snapshot_id,不检查pass)。
Block A 的阶段专属说明:
mantis-chain不是仅处理发现结果的阶段(它不是 report/calibrate/reflect),因此 Block A 会运行步骤 1–6,并从--snapshot_root/--snapshot_id/ stateactive_snapshot解析CODE_ROOT、SNAPSHOT_ID和snapshot_pinned。- 此阶段仅将新的发现结果 JSON 写入
workspace/findings/(相对于 STATE,见 Block A 步骤 3)。它绝不会写入CODE_ROOT下,因此 Block A 步骤 4 自动满足。如果你选择重新检查某个组成部分的code_paths以核验链,则在CODE_ROOT下按相对于快照的路径读取(见 Block A 步骤 3),切勿在切换目录后向其中写入。 - 链式发现结果的
code_paths会从其组成部分原样复制(这些路径已相对于快照)。不要重新添加前缀、重新解析或重新编号。
快照匹配检查(用于选择组成部分)
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.)
在下方步骤 1 中,对每个候选组成部分发现结果运行一次 Block B,并将该发现结果的 discovery_commit 作为 F.discovery_commit。
读取当前批次中已验证的发现结果,并探索是否能将多个看似严重性较低或彼此不同的漏洞按顺序组合起来,从而造成影响更大的安全失陷。
按以下方式执行链式分析阶段:
-
加载原语和已验证的发现结果:
- 读取
workspace/findings/目录中的 JSON 文件。筛选出已通过验证的发现项(例如,状态为"VALID",且可行性为"VIABLE"、"CONDITIONAL_VIABLE"或"SAMPLE_OR_TEST")。 - 快照资格门槛(新增——根据是否存在
active_snapshot采用三态模型):- MODE-OFF(状态中没有
active_snapshot——未请求--sync,即今天的默认行为):完全跳过此门槛:上述验证/可行性筛选中通过的每个发现项都符合条件,可作为组成项,与今天的行为完全相同。照常构建链。 - HALT(
active_snapshot存在,但snapshot_pinned == false):本轮不要构建任何链。在 HALT 模式下,发现项的定位信息和前置/后置条件可能已经过时(代码树发生了竞争变更或无法固定),因此无法生成权威的链判定。记录“no chain: HALT mode (active_snapshot present, snapshot_pinned=false)”,并正常停止链构建阶段(不生成链文件)。这与下方的最低资格保护逻辑一致——链要求组成项已针对快照完成验证,而 HALT 模式无法满足这一要求。 - PINNED(
active_snapshot存在,且snapshot_pinned == true):对每个此类发现项,使用其discovery_commit运行快照匹配检查(区块 B)。仅保留返回 MATCHED 的发现项作为符合条件的组成项。返回 NOT_MATCHED 的发现项——包括discovery_commit缺失/为空、值为字面量"MIXED"或与当前SNAPSHOT_ID不同的任何发现项——都是在不同代码快照下发现的;其file:line定位信息和前置/后置条件可能已不再成立,因此本轮不得将其用作链环节。
- MODE-OFF(状态中没有
- **最低资格保护(新增):**如果符合条件的组成项少于 2 个,本轮不要构建任何链——链至少需要两个已针对同一快照完成验证的环节。记录“no chain: fewer than 2 snapshot-matched constituents”,并正常停止链构建阶段(这不是错误;只需不生成链文件)。当可用于构链的发现项少于两个时,这与今天的行为一致。
- 阅读 Markdown 知识库(
workspace/kb/entities/和workspace/kb/vulnerabilities/),以识别可能本身并非漏洞、但可作为踏脚石的架构要素(例如,“用户可以控制文件上传路径”“服务以 root 身份运行”)。
- 读取
-
跨发现项分析(链式分析矩阵):
- 分析每个已验证发现项的前置条件和后置条件。
- 询问:发现项 A 的输出或副作用能否满足触发发现项 B 所需的严格前置条件?
- 值得寻找的示例链:
- **路径遍历 + 宽松权限 = RCE:**低严重性的路径遍历(发现项 A)允许写入
/tmp,但另一项错误配置(发现项 B)允许 cron 作业执行/tmp中的脚本。 - **XSS + CSRF = 账户接管:**存储型 XSS(发现项 A)可用于窃取反 CSRF 令牌,从而执行会更改状态的操作(发现项 B)。
- **信息泄露(内存泄露)+ 缓冲区溢出 = ASLR 绕过:**信息泄露(发现项 A)会暴露基址指针,满足利用栈缓冲区溢出漏洞(发现项 B)的前置条件。
- **路径遍历 + 宽松权限 = RCE:**低严重性的路径遍历(发现项 A)允许写入
-
构建“超级发现项”:
- 如果发现了可行的漏洞利用链,不要修改或删除 原始的孤立发现。它们仍需单独修复。
- 幂等性检查(在生成 UUID 之前运行): 应用上述幂等性
保证——扫描当前的
workspace/findings/、workspace/archive/findings_pass_*/以及旧版的workspace/archive/loop*_findings/,查找其组成项序列 与此链的有序组成项序列相等的链。如果所有组成项都有signature,则比较有序的signature序列,并要求满足幂等性保证中 描述的、包含行号的逐组成项code_paths决胜条件(仅匹配 signature 序列不足以跳过);如果任一组成项缺少signature,则回退到 有序的组成项 UUID 序列(当前行为)。只有匹配满足决胜条件时,才 跳过此链(不创建文件),并继续处理下一个候选项。 - 相反,生成一个全新的 UUID,并在
workspace/findings/<new_uuid>.json中创建新的发现 JSON 文件。 - 组成发现项:必须在结构化的
"constituent_findings"属性中记录 组成发现项 UUID 数组(例如["UUID_A", "UUID_B"])。这会清楚地记录漏洞利用链的关联关系。 - 确定发现快照(
discovery_commit):根据符合条件的组成项 (即步骤 1 中保留的组成项)设置链发现项的discovery_commit:- MODE-OFF 短路(3 态规则): 如果状态中不存在
active_snapshot(MODE-OFF——未请求--sync),则完全省略链发现项中的discovery_commit。在 MODE-OFF 中,researcher 会在每个组成项中 省略discovery_commit(researcher:317,340-341:“如果active_snapshot不存在或snapshot_pinned为 false,则完全省略 此键”),因此每个组成项的discovery_commit都缺失或为空。所以下方的 SAME/MIXED 逻辑会在每条链上写入字面值"MIXED"——这是 Phase 1 中不存在的快照时代产物,也违反了 MODE-OFF 的“与当前行为 逐字节一致”保证(schema.json:4)。链自身的 schema 注释 (chain:307)写明“在旧版/未固定运行中不存在”,证实 MODE-OFF 下预期行为是省略,而不是写入"MIXED"。(功能影响较低——schema.json:232会将“缺失”和"MIXED"都视为 NOT_MATCHED——但 模式纯粹性很重要。)在 MODE-OFF 下,不要写入"MIXED"或任何其他值。 - 如果所有符合条件的组成项都具有相同的非空
discovery_commit值,则将链的discovery_commit设为该确切字符串。 - 如果组成项的
discovery_commit值彼此不同,或任一符合条件的组成项 缺少discovery_commit或其值为空(仅在 PINNED/HALT 模式下可能出现—— 在 MODE-OFF 下,上述短路已省略此字段),则将链的discovery_commit设为字面字符串"MIXED"。不要臆造、哈希或模糊 规范化此值——复制确切字符串或写入字面值"MIXED"。原因:在固定 模式下,所有符合条件的组成项都为 MATCHED,因此它们都共享当前的SNAPSHOT_ID,链也会继承该值(后续针对该链的 Block B 将在本轮 返回 MATCHES)。"MIXED"哨兵值会使 Block B 对该链返回 NOT_MATCHED(Block B 步骤 2 会将字面值"MIXED"视为 NOT_MATCHED)——当无法证明链的关联项对应单个快照时,这是安全的分支。
- MODE-OFF 短路(3 态规则): 如果状态中不存在
- 计算
signature(链的确定性内容身份哈希):- 收集每个符合条件的组成发现项的
signature字段(与步骤 1 中用于discovery_commit的组成项集合相同)。 - 如果每个符合条件的组成项都有非空的
signature:按字典序 (升序)对组成项的 signature 排序,然后计算signature=sha256("chain|" + "|".join(sorted_constituent_signatures))的前 16 个十六进制字符。"chain|"前缀可避免与非链发现项的signature发生冲突(后者根据标题、cwe 和目标哈希而来,输入 域不同)。排序使链的 signature 不受组成项顺序影响。 - 如果任一符合条件的组成项缺少
signature字段(旧版/尚未升级的 发现项):将链的signature设为空字符串,并从 JSON 中 省略该字段(下游使用者随后会按schema.json:256回退到基于 UUID 的 折叠/去重)。这是安全分支——不会导致错误折叠,只可能造成过度报告。 - 在创建链时仅计算一次 signature,之后绝不要重新计算、编辑或臆造
该值(与
discovery_commit以及普通发现项的researcher:241-242规则相同)。
- 收集每个符合条件的组成发现项的
- 计算
lineage_id(跨轮次链谱系):- 扫描
workspace/archive/findings_pass_*/和旧版的workspace/archive/loop*_findings/,查找任何归档的链发现项 JSON (即constituent_findings数组非空的任何发现项),其signature字段与此链计算出的signature完全相等。 - 如果找到匹配项:将
lineage_id设为归档祖先项的lineage_id(继承该谱系链,以便报告能跨轮次折叠)。如果多个归档链共享相同的signature,则从最新(轮次编号最高)的祖先项继承。所有具有相同链signature的祖先项都应共享相同的lineage_id;如果它们不一致, 则从最新的祖先项继承并记录警告。 - 如果没有匹配项(包括此链的
signature为空字符串时),则将lineage_id设为新的 UUIDv4。 - 这些是相对于状态的路径(Block A 步骤 3)——从
--state_root/workspace/archive/下读取,绝不要从 CODE_ROOT 下 读取。(扫描目标和算法形式与普通发现项的researcher:246-272相同—— 链只是不执行基于文件名规范化的重命名回退,因为链的身份是其组成项 的集合,而非单个文件路径。)
- 扫描
- 确定入口点权限:链的
privileges_required字段必须表示启动链的 第一步(入口点)所需的权限级别。例如,如果链从未认证的漏洞利用 (NONE)开始,进而获得管理员访问权限,再利用该权限触发 RCE,则链的privileges_required必须设为NONE。 - 确定攻击者位置:链的
attacker_position字段必须继承链的入口点/ 第一个组成发现项的攻击者位置。 - 确定用户交互要求:如果入口点或链中的任一步骤需要用户交互,则链的
user_interaction字段必须设为REQUIRED。只有在整条链无需点击即可 完成时,才应设为NONE。 - 确定状态:将
status设为"VALID"。 - 确定生产环境可行性:从组成发现项继承。如果任一组成项为
"SAMPLE_OR_TEST",则设为"SAMPLE_OR_TEST"。否则,如果任一组成项为"CONDITIONAL_VIABLE",则设为"CONDITIONAL_VIABLE"。否则,设为"VIABLE"。 - 确定复现状态:从组成发现项继承:
- 如果任一组成项的
repro_status为"failed_to_reproduce"或"not_attempted",则设为"not_attempted"。 - 否则,如果所有组成项的
repro_status都为"reproduced"或"statically_confirmed",则设为"statically_confirmed"。 - 漏洞利用链绝不能继承
"reproduced"(组成项的复现成功并不能证明 端到端链条有效)。
- 如果任一组成项的
链发现项架构格式(每个文件)
{ "id": "A unique identifier generated for this chain finding. Must match filename.", "title": "Exploit Chain: [Impact] via [Finding A] and [Finding B]", "description": "Step-by-step documentation of the exploit chain. Start with Step 1 (Triggering Finding A) and explain how its outcome feeds into Step N (Triggering Finding Z).", "impact": "The combined, escalated impact of the chain (e.g., Remote Code Execution, Full Database Exfiltration). This should be higher than the individual findings.", "severity": "CRITICAL / HIGH", "privileges_required": "NONE / LOW / HIGH", "user_interaction": "NONE / REQUIRED", "code_paths": [ "relative/file/path_A.c:line_number", "relative/file/path_B.c:line_number" ], "attacker_position": "EXTERNAL / LOCAL / etc. (inherited from entry point)", "mitigation": "Recommended strategy to break the chain. Usually involves fixing at least one, if not all, of the underlying links.", "status": "VALID", "production_viability": "VIABLE / SAMPLE_OR_TEST / CONDITIONAL_VIABLE", "repro_status": "statically_confirmed / not_attempted", "constituent_findings": ["UUID_A", "UUID_B"], "signature": "First 16 hex chars of sha256(\"chain|\" + \"|\".join(sorted(constituent_signatures))) if EVERY constituent has a non-empty `signature`; else the EMPTY STRING (absent-equivalent -> downstream falls back to UUID-only behavior). Computed ONCE at chain creation and NEVER recomputed (same rule as discovery_commit). See the signature/lineage computation steps above.", "lineage_id": "Inherited from the most-recent archived chain finding whose `signature` equals this chain's `signature` (scan workspace/archive/findings_pass_*/ AND workspace/archive/loop*_findings/); else a fresh UUIDv4. See the signature/lineage computation steps above.", "discovery_commit": "The SNAPSHOT_ID shared by all constituents, or the literal \"MIXED\" if they differ / are missing (only computed in PINNED/HALT mode). OMITTED (absent) on legacy/unpinned runs (MODE-OFF) — see the MODE-OFF short-circuit in the discovery_commit computation step above.", "history": [ { "stage": "chainer", "action": "created", "details": "Constructed by chaining findings [UUID_A] and [UUID_B].", "pass_number": <current_pass_number>, "timestamp": "<current_iso8601_timestamp>" } ] } -
链去重标记:
- 为确保
/mantis-dedupe将这些链与原始发现项区别处理,请确保"title"和"history"字段中醒目地包含单词 "Chain",如架构所示。
- 为确保
确保将所有新构建的链文件写入
workspace/findings/ 目录。完成后,通知用户。