mantis-chain
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseVulnerability Chainer (/mantis-chain)
漏洞利用链构建工具(/mantis-chain)
System Goal
系统目标
Exploit Chain Architect. Analyzes isolated, individually-validated security
findings and historical knowledge base primitives to identify and construct
complex, multi-step exploit chains.
漏洞利用链架构师。分析已独立验证的安全检测结果和历史知识库原语,识别并构建复杂的多步骤漏洞利用链。
Command Definition
命令定义
- Command:
/mantis-chain - Description: Analyzes individual security findings to identify and construct complex exploit chains.
- Arguments (all optional; supplied by the orchestrator per the harness
lifecycle contract — see "Non-JSON Contracts"):
schema.json- /
--snapshot_root— the pinned, immutable code snapshot root for this pass. Consumed by Block A.SNAPSHOT_ROOT - /
--snapshot_id— the snapshot identity string for this pass. Consumed by Block A (sentinel) and Block B (match check).SNAPSHOT_ID - — parent directory of
--state_root. When absent, defaults to the current directory (Block A step 1c/1d).workspace/ - — an already-prepared tree that OVERRIDES the snapshot root (rarely used by this stage; honored by Block A step 1a). When NONE of these are passed, Block A falls back to
--target_rootinactive_snapshot; if that is absent too, the stage runs MODE-OFF (workspace/.mantis_state.json) and behaves exactly as today (see Backward-compat).snapshot_pinned = false
- 命令:
/mantis-chain - 描述: 分析单个安全检测结果,识别并构建复杂的漏洞利用链。
- 参数(均为可选;由编排器根据工具生命周期协议提供——详见 中的“非JSON协议”):
schema.json- /
--snapshot_root—— 本次扫描的固定不可变代码快照根目录。由模块A使用。SNAPSHOT_ROOT - /
--snapshot_id—— 本次扫描的快照标识字符串。由模块A(哨兵检查)和模块B(匹配检查)使用。SNAPSHOT_ID - ——
--state_root的父目录。若未提供,默认值为当前目录(模块A步骤1c/1d)。workspace/ - —— 已准备好的目录树,将覆盖快照根目录(本阶段极少使用;模块A步骤1a会遵循此设置)。若未传递上述任何参数,模块A会回退到
--target_root中的workspace/.mantis_state.json;若该值也不存在,本阶段将运行MODE-OFF模式(active_snapshot),行为与当前版本完全一致(详见向后兼容说明)。snapshot_pinned = false
Input/Output Contract
输入/输出协议
- Reads:
- (validated finding JSON files where status is
workspace/findings/, and viability is"VALID","VIABLE", or"CONDITIONAL_VIABLE")."SAMPLE_OR_TEST" - and
workspace/kb/entities/*.md(knowledge base primitives).workspace/kb/vulnerabilities/*.md - (to track current loop pass).
workspace/.mantis_state.json
- Writes:
- Net-new exploit chain finding JSON files to
. Original findings are left unmodified.
workspace/findings/<new_uuid>.json
- Net-new exploit chain finding JSON files to
- Preconditions:
- Validated or viable findings must exist in .
workspace/findings/
- Validated or viable findings must exist in
- Idempotency Guarantee:
- Before writing a new exploit chain finding, the skill must check existing
exploit chain findings by comparing the constituent finding sequence. Scan
BOTH the current directory AND every archived pass under
workspace/findings/AND legacyworkspace/archive/findings_pass_*/(all STATE-RELATIVE — never prefixworkspace/archive/loop*_findings/). A chain JSON is any finding whoseCODE_ROOTarray is present and non-empty. If a chain with the EXACT same ordered constituent sequence already exists in EITHER location, skip creating a duplicate.constituent_findings - Signature-keyed idempotency (preferred) — with a code_paths tiebreak: If
ALL constituent findings have a field, compare the ordered constituent
signaturesequence (not UUIDs). Treat two chains as the SAME (and skip creating the new one) ONLY IF their orderedsignaturesequences are equal AND, for each corresponding constituent pair, at least onesignatureentry (line-inclusive, i.e. compared WITH its trailingcode_paths) is identical. If the signature sequences match but any corresponding constituent's:linediffer, the chains are DISTINCT — create the new chain. (Over-reporting is safe; acode_pathscollision must never suppress a genuinely new exploit chain, becausesignaturestrips line numbers and can collide between distinct same-file bugs.)signature - UUID fallback (today's behavior): If ANY constituent finding lacks a
field, fall back to comparing the ordered constituent UUID sequence exactly as before. This preserves today's behavior for legacy / un-upgraded findings.
signature - This archive scan is now fully effective with stable finding signatures: two
chains whose constituents share the same signatures are correctly
deduplicated across passes. When signatures are absent (legacy findings),
the scan remains HARMLESS — it cannot falsely suppress a legitimate new
chain (no silent dropped result) and simply prevents exact-UUID
re-duplication within a resumed / rerun pass. If the directory does not exist, treat the archived set as empty and proceed.
workspace/archive/
- Before writing a new exploit chain finding, the skill must check existing
exploit chain findings by comparing the constituent finding sequence. Scan
BOTH the current
- 读取:
- (状态为
workspace/findings/、可行性为"VALID"、"VIABLE"或"CONDITIONAL_VIABLE"的已验证检测结果JSON文件)。"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数组的检测结果JSON均为链结果。如果任一位置已存在具有完全相同有序组成序列的链,则跳过创建重复项。constituent_findings - 基于签名的幂等性(推荐)——结合代码路径作为决胜条件: 如果所有组成检测结果都包含 字段,则比较有序的组成
signature序列(而非UUID)。仅当两个链的有序signature序列相等,且每个对应组成项至少有一个signature条目(包含行号,即比较时保留末尾的code_paths)完全相同时,才将它们视为相同(并跳过创建新链)。如果签名序列匹配但对应组成项的:line不同,则这两个链是不同的——需创建新链。(过度报告是安全的;code_paths冲突绝不能抑制真正的新漏洞利用链,因为signature会去除行号,可能在不同文件的不同漏洞之间发生冲突。)signature - UUID回退(当前行为): 如果任一组成检测结果缺少 字段,则回退到之前的行为,即精确比较有序的组成UUID序列。这将为旧版/未升级的检测结果保留当前行为。
signature - 借助稳定的检测结果签名,本次归档扫描现在完全有效:组成项签名相同的两个链会在不同扫描次数之间被正确去重。当签名不存在(旧版检测结果)时,扫描仍然是无害的——它不会错误地抑制合法的新链(不会静默丢弃结果),只是防止在恢复/重新运行的扫描中重复创建完全相同UUID的链。如果 目录不存在,则将归档集视为空并继续执行。
workspace/archive/
- 在写入新的漏洞利用链检测结果前,必须通过比较组成检测结果的序列,检查已有的漏洞利用链检测结果。扫描当前
Instructions
操作说明
Locator Resolution (run first — all stages)
定位器解析(所有阶段均需首先运行)
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.[!NOTE] CURRENT-PASS CHECK (defensive; the binding guarantee is on the harness perScenario 2): ifmantis-pipeline-adapteris present ANDactive_snapshot, treat the snapshot as STALE for this pass — STOP "stale active_snapshot: pass mismatch" or degrade as HALT (active_snapshot.pass != state.pass_numbereffectively false: no authoritative verdicts, Block B NOT_MATCHED, reproducesnapshot_pinned). This catches a custom harness that preservednot_attemptedacross the Stage 15 pass increment without re-pinning. The reference meta-agent re-pins every pass, so this check never fires there. Block B itself cannot detect this (it isactive_snapshot-only, notsnapshot_id-aware).pass
Stage-specific notes for Block A:
- is NOT a findings-only stage (it is not report/calibrate/reflect), so Block A runs steps 1–6 and resolves
mantis-chain,CODE_ROOT, andSNAPSHOT_IDfromsnapshot_pinned/--snapshot_root/ state--snapshot_id.active_snapshot - This stage WRITES only new finding JSON to (STATE-RELATIVE, Block A step 3). It never writes under
workspace/findings/, so Block A step 4 is satisfied trivially. If you optionally re-inspect a constituent'sCODE_ROOTto sanity-check a chain, read them SNAPSHOT-RELATIVE undercode_paths(Block A step 3) and never cd-and-write there.CODE_ROOT - A chain finding's are COPIED verbatim from its constituents (they are already SNAPSHOT-RELATIVE). Do not re-prefix, re-resolve, or renumber them.
code_paths
LOCATOR RESOLUTION(读取任何目标代码或工件之前):
0. 角色:如果本工具从不读取目标源代码(仅用于报告、校准、反思),则属于仅处理检测结果的阶段:跳过步骤2-6;仍需从状态中读取active_snapshot以进行来源标注;切勿仅因代码根目录未设置而停止运行。
1. 按以下优先级确定CODE_ROOT:
a. 如果本次调用传递了--target_root,则CODE_ROOT = --target_root。该设置具有权威性,将覆盖SNAPSHOT_ROOT和状态回退值(当调用者提供已准备好的目录树时使用,例如已打补丁的影子目录)。
b. 否则,如果传递了--snapshot_root(或SNAPSHOT_ROOT),则使用该值。
c. 否则读取state_root/workspace/.mantis_state.json(state_root来自传递的--state_root,否则为相对于当前目录的./workspace/...)中的active_snapshot.root / .snapshot_id / .snapshot_pinned。
d. 否则(无参数且无可用的active_snapshot):CODE_ROOT = 当前目录,将snapshot_pinned视为false(MODE-OFF模式)。请勿停止运行。
2. 哨兵检查(仅当snapshot_pinned为true且未采用路径1a时执行):
验证CODE_ROOT/.mantis_snapshot_id是否存在且等于SNAPSHOT_ID。如果缺失或不匹配 -> 停止运行并提示"snapshot sentinel mismatch"。(--target_root目录树(路径1a)是故意修改的,无需进行哨兵检查。)
3. 路径字段:
- 快照相对路径(在CODE_ROOT下读取):code_paths条目;计划中的target_files为文件路径。仅去除末尾的":<数字>"。包含"://"的code_paths条目是URL/端点,而非文件路径。格式不是<现有路径>:<整数>的code_paths条目是非源代码定位器(符号/偏移量/端点):仅检查工件/符号是否存在;跳过所有行范围和行存在性逻辑。
- 状态相对路径(在state_root/workspace下读取/写入,切勿添加CODE_ROOT前缀):kb_references、repro_file_path、reattack_file_path、辅助脚本、报告文件以及所有状态/检测结果JSON。
4. 当snapshot_pinned为true时,切勿在CODE_ROOT下写入内容。任何编译、生成或写入工件的命令必须在CODE_ROOT的私有影子副本(mktemp -d)中运行,绝不能以CODE_ROOT为工作目录。只读检查可以进入CODE_ROOT目录。
5. 版本控制系统元数据例外:历史日志提取以及任何版本控制系统的diff/blame命令必须在实时仓库根目录(包含.git/.hg/.repo)中运行,而非CODE_ROOT(快照副本会去除版本控制系统元数据)。切勿仅因CODE_ROOT缺少.git/.hg/.repo而停止运行。
6. 每个shell命令都使用绝对路径,并在调用时设置自己的工作目录。切勿假设工作目录在调用之间保持不变。[!NOTE] 当前扫描次数检查(防御性检查;工具的绑定保证基于场景2): 如果存在mantis-pipeline-adapter且active_snapshot,则将该快照视为当前扫描的过期快照——停止运行并提示"stale active_snapshot: pass mismatch",或降级为HALT模式(active_snapshot.pass != state.pass_number实际为false:无权威性结论,模块B返回NOT_MATCHED,复现状态为snapshot_pinned)。这会捕获自定义工具在阶段15扫描次数递增时保留not_attempted但未重新固定的情况。参考元代理每次扫描都会重新固定快照,因此该检查在那里永远不会触发。模块B本身无法检测到这一点(它仅识别active_snapshot,不感知snapshot_id)。pass
模块A的阶段特定说明:
- 不是仅处理检测结果的阶段(它不用于报告/校准/反思),因此模块A会运行步骤1–6,并从
mantis-chain/--snapshot_root/ 状态中的--snapshot_id解析active_snapshot、CODE_ROOT和SNAPSHOT_ID。snapshot_pinned - 本阶段仅向写入新的检测结果JSON(状态相对路径,模块A步骤3)。它永远不会在
workspace/findings/下写入内容,因此模块A步骤4自动满足。如果您选择重新检查组成项的CODE_ROOT以验证链的合理性,请在code_paths下以快照相对路径读取这些内容(模块A步骤3),切勿进入该目录并写入内容。CODE_ROOT - 链检测结果的直接从其组成项复制(它们已经是快照相对路径)。请勿重新添加前缀、重新解析或重新编号。
code_paths
Snapshot Match Check (used to select constituents)
快照匹配检查(用于选择组成项)
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.)You will run Block B once per candidate constituent finding in Step 1 below,
using that finding's as .
discovery_commitF.discovery_commitRead the current batch of validated findings and explore whether multiple
seemingly low-severity or disparate vulnerabilities can be sequentially combined
to achieve a higher-impact compromise.
Execute the chaining stage as follows:
-
Load Primitives & Validated Findings:
- Read the JSON files in the directory. Filter for findings that have passed validation (e.g., status is
workspace/findings/and viability is"VALID","VIABLE", or"CONDITIONAL_VIABLE")."SAMPLE_OR_TEST" - Snapshot eligibility gate (NEW — branches on presence, 3-state model):
active_snapshot- MODE-OFF (no in state — no
active_snapshotwas requested, today's default): SKIP this gate entirely: every finding that passed the validation/viability filter above is an eligible constituent, exactly as today. Build chains normally.--sync - HALT (IS present but
active_snapshot): do NOT build any chains this pass. In HALT, findings' locators and pre/post-conditions may be stale (the tree raced or could not be pinned), so no authoritative chain verdicts may be produced. Log "no chain: HALT mode (active_snapshot present, snapshot_pinned=false)" and stop the chaining stage cleanly (produce no chain files). This mirrors the minimum-eligibility guard below — a chain requires proven-against-snapshot constituents, which HALT cannot provide.snapshot_pinned == false - PINNED (present AND
active_snapshot): run the Snapshot Match Check (Block B) on EACH such finding, using itssnapshot_pinned == true. Keep ONLY findings that return MATCHED as eligible constituents. A finding that returns NOT_MATCHED — including any finding whosediscovery_commitis absent/empty, is the literaldiscovery_commit, or differs from the current"MIXED"— was discovered against a DIFFERENT code snapshot; itsSNAPSHOT_IDlocators and its pre/post-conditions may no longer hold, so it MUST NOT be used as a chain link this pass.file:line
- MODE-OFF (no
- Minimum-eligibility guard (NEW): If fewer than 2 eligible constituents remain, do NOT construct any chain this pass — a chain requires at least two links proven against the SAME snapshot. Log "no chain: fewer than 2 snapshot-matched constituents" and stop the chaining stage cleanly (this is not an error; simply produce no chain files). This mirrors today's behavior whenever there are fewer than two chainable findings.
- Read the Markdown Knowledge Base (and
workspace/kb/entities/) to identify architectural primitives that might not be bugs on their own, but could serve as stepping stones (e.g., "User controls file upload path", "Service runs as root").workspace/kb/vulnerabilities/
- Read the JSON files in the
-
Cross-Finding Analysis (The Chaining Matrix):
- Analyze the preconditions and postconditions of each validated finding.
- Ask: Can the output or side-effect of Finding A satisfy the strict precondition required to trigger Finding B?
- Example Chains to look for:
- Path Traversal + Loose Permissions = RCE: A low-severity path
traversal (Finding A) allows writing to , but a separate misconfiguration (Finding B) allows a cron job to execute scripts in
/tmp./tmp - XSS + CSRF = Account Takeover: A stored XSS (Finding A) can be used to harvest an anti-CSRF token to execute a state-changing action (Finding B).
- Info Leak (Memory Revelation) + Buffer Overflow = ASLR Bypass: An info leak (Finding A) reveals base pointers, satisfying the precondition to exploit a stack buffer overflow (Finding B).
- Path Traversal + Loose Permissions = RCE: A low-severity path
traversal (Finding A) allows writing to
-
Construct "Super Findings":
- If a viable exploit chain is discovered, do NOT modify or delete the original isolated findings. They still need to be patched individually.
- Idempotency check (run BEFORE minting a UUID): Apply the Idempotency
Guarantee above — scan current AND
workspace/findings/AND legacyworkspace/archive/findings_pass_*/for a chain whose constituent sequence equals this chain's ordered constituent sequence. If ALL constituents haveworkspace/archive/loop*_findings/, compare orderedsignaturesequences AND require the per-constituent line-inclusivesignaturetiebreak described in the Idempotency Guarantee (a signature-sequence match ALONE is NOT enough to skip); if ANY constituent lackscode_paths, fall back to ordered constituent UUID sequences (today's behavior). Only if a match satisfies the tiebreak, SKIP this chain (do not create a file) and move on to the next candidate.signature - Instead, generate a net-new UUID and create a new finding JSON file in
.
workspace/findings/<new_uuid>.json - Constituent Findings: You must record the array of constituent finding
UUIDs in the structured property (e.g.,
"constituent_findings"). This clearly documents the links of the exploit chain.["UUID_A", "UUID_B"] - Determine Discovery Snapshot (): Set the chain finding's
discovery_commitfrom its ELIGIBLE constituents (the ones you kept in Step 1):discovery_commit- MODE-OFF short-circuit (3-state rule): if is ABSENT in state (MODE-OFF — no
active_snapshotwas requested), OMIT--syncon the chain finding entirely. In MODE-OFF, researcher OMITSdiscovery_commiton every constituent (discovery_commit: "OMIT this key entirely if active_snapshot is absent or snapshot_pinned is false"), so every constituent has a missing/emptyresearcher:317,340-341. The SAME/MIXED logic below would thus write the literaldiscovery_commiton every chain — a snapshot-era artifact that did not exist in Phase 1 and violates MODE-OFF's "byte-for-byte today's behavior" guarantee ("MIXED"). The chain's own schema comment (schema.json:4) says "Absent on legacy/unpinned runs," confirming the intended MODE-OFF behavior is omission, notchain:307. (Functional impact is low —"MIXED"treats both "absent" andschema.json:232as NOT_MATCHED — but mode purity matters.) Do NOT write"MIXED"or any value in MODE-OFF."MIXED" - If ALL eligible constituents have the SAME non-empty value, set the chain's
discovery_committo that exact string.discovery_commit - If the constituents' values DIFFER from one another, OR any eligible constituent has a missing/empty
discovery_commit(only reachable in PINNED/HALT mode — in MODE-OFF, the short-circuit above already OMITted the field), set the chain'sdiscovery_committo the literal stringdiscovery_commit. Do NOT invent, hash, or fuzzy- normalize this value — copy an exact string or write the literal"MIXED". Rationale: in pinned mode every eligible constituent is MATCHED, so they all share the current"MIXED", and the chain inherits it (a later Block B on the chain then MATCHES this pass). TheSNAPSHOT_IDsentinel makes Block B return NOT_MATCHED for the chain (Block B step 2 treats the literal"MIXED"as NOT_MATCHED) — the safe branch for a chain whose links cannot be proven against a single snapshot."MIXED"
- MODE-OFF short-circuit (3-state rule): if
- Compute (deterministic content-identity hash for the chain):
signature- Collect the field from every ELIGIBLE constituent finding (the same set used for
signaturein Step 1).discovery_commit - If EVERY eligible constituent has a non-empty : sort the constituent signatures lexicographically (ascending), then compute
signature= first 16 hex characters ofsignature. Thesha256("chain|" + "|".join(sorted_constituent_signatures))prefix prevents any collision with a non-chain finding's"chain|"(which is hashed from title+cwe+target, a different input domain). Sorting makes the chain signature invariant under constituent re-ordering.signature - If ANY eligible constituent lacks a field (legacy / un-upgraded finding): set the chain's
signatureto the EMPTY STRING and OMIT it from the JSON (downstream consumers then fall back to UUID-based fold/dedupe persignature). This is the safe branch — it can never cause a wrong fold, only an over-report.schema.json:256 - Compute the signature ONCE at chain creation and NEVER recompute, edit,
or invent it (same rule as and as
discovery_commitfor regular findings).researcher:241-242
- Collect the
- Compute (cross-pass chain lineage):
lineage_id- Scan AND legacy
workspace/archive/findings_pass_*/for any archived chain finding JSON (any finding with a non-emptyworkspace/archive/loop*_findings/array) whoseconstituent_findingsfield EXACTLY equals this chain's computedsignature.signature - If a match is found: = the archived ancestor's
lineage_id(inherit the lineage chain so report can fold across passes). If MULTIPLE archived chains share the samelineage_id, inherit from the MOST RECENT (highest pass number) ancestor. All ancestors with the same chainsignatureSHOULD share the samesignature; if they don't, inherit from the most recent one and log a warning.lineage_id - If no match (including when this chain's is the EMPTY STRING):
signature= a fresh UUIDv4.lineage_id - These are STATE-RELATIVE paths (Block A step 3) — read under
, NEVER under CODE_ROOT. (Same scan targets and same algorithm shape as
--state_root/workspace/archive/for regular findings — chains simply skip the basename-normalized rename fallback, because a chain's identity is the set of its constituents, not a single file path.)researcher:246-272
- Scan
- Determine Entry Point Privileges: The field for the chain must represent the privilege level required to initiate the first step of the chain (the entry point). For example, if the chain starts with an unauthenticated exploit (NONE) that leads to admin access, which is then used to trigger RCE, the chain's
privileges_requiredmust be set toprivileges_required.NONE - Determine Attacker Position: The field for the chain must inherit the attacker position from the entry point / first constituent finding of the chain.
attacker_position - Determine User Interaction Requirement: The field for the chain must be set to
user_interactionif the entry point or any step in the chain requires user interaction. It should only be set toREQUIREDif the entire chain is zero-click.NONE - Determine Status: Set to
status."VALID" - Determine Production Viability: Inherit from constituent findings. If
any constituent is , set to
"SAMPLE_OR_TEST". Else if any constituent is"SAMPLE_OR_TEST", set to"CONDITIONAL_VIABLE". Otherwise, set to"CONDITIONAL_VIABLE"."VIABLE" - Determine Reproduction Status: Inherit from constituent findings:
- If any constituent has a of
repro_statusor"failed_to_reproduce", set to"not_attempted"."not_attempted" - Otherwise, if all constituents have a of
repro_statusor"reproduced", set to"statically_confirmed"."statically_confirmed" - An exploit chain must never inherit (as reproduction of constituents does not prove the end-to-end chain works).
"reproduced"
- If any constituent has a
Chain Findings Schema Format (Per File)
json{ "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>" } ] } -
Chain Deduplication Tagging:
- To ensure treats these chains differently than raw findings, ensure the word "Chain" is prominently featured in the
/mantis-dedupeand"title"fields as shown in the schema."history"
- To ensure
Ensure any newly constructed chain files are written to the
directory. When complete, notify the user.
workspace/findings/检测结果F的SNAPSHOT MATCH CHECK(决定MATCHED vs NOT_MATCHED):
1. 如果snapshot_pinned为false -> NOT_MATCHED。停止检查。
2. 读取F.discovery_commit:
- 缺失或为空或字面为"MIXED" -> NOT_MATCHED。
- 与SNAPSHOT_ID不完全相等 -> NOT_MATCHED。
- 与SNAPSHOT_ID完全相等 -> MATCHED。
没有其他途径得到MATCHED;绝不进行模糊比较。全局的"默认字段并继续"向后兼容规则不适用于discovery_commit:缺失=NOT_MATCHED。(没有单独的"脏"检查:脏目录树的SNAPSHOT_ID已嵌入工作目录内容哈希,因此同一次扫描中的检测结果会匹配,跨扫描的裸提交检测结果则不会。)您需要对步骤1中的每个候选组成检测结果运行一次模块B,使用该检测结果的作为。
discovery_commitF.discovery_commit读取当前批次的已验证检测结果,探索多个看似低严重性或不相关的漏洞是否可以按顺序组合,以实现更高影响的攻击。
按以下步骤执行链构建阶段:
-
加载原语与已验证检测结果:
- 读取目录中的JSON文件。筛选已通过验证的检测结果(例如,状态为
workspace/findings/且可行性为"VALID"、"VIABLE"或"CONDITIONAL_VIABLE")。"SAMPLE_OR_TEST" - 快照资格检查(新增——基于存在性的三态模型):
active_snapshot- MODE-OFF模式(状态中无——未请求
active_snapshot,当前默认模式):完全跳过此检查:所有通过上述验证/可行性筛选的检测结果均为合格组成项,与当前版本完全一致。正常构建链。--sync - HALT模式(存在但
active_snapshot):本次扫描不构建任何链。在HALT模式下,检测结果的定位器和前置/后置条件可能已过期(目录树已更改或无法固定),因此无法生成权威性的链结论。记录日志"no chain: HALT mode (active_snapshot present, snapshot_pinned=false)"并干净地停止链构建阶段(不生成任何链文件)。这与下面的最低资格检查一致——链需要针对同一快照验证的至少两个组成项,而HALT模式无法提供。snapshot_pinned == false - PINNED模式(存在且
active_snapshot):对每个此类检测结果运行快照匹配检查(模块B),使用其snapshot_pinned == true。仅保留返回MATCHED的检测结果作为合格组成项。返回NOT_MATCHED的检测结果——包括discovery_commit缺失/为空、字面为discovery_commit或与当前"MIXED"不同的检测结果——是针对不同代码快照发现的;其SNAPSHOT_ID定位器和前置/后置条件可能不再有效,因此本次扫描绝不能将其用作链环节。file:line
- MODE-OFF模式(状态中无
- 最低资格检查(新增): 如果剩余合格组成项少于2个,本次扫描不构建任何链——链需要至少两个针对同一快照验证的环节。记录日志"no chain: fewer than 2 snapshot-matched constituents"并干净地停止链构建阶段(这不是错误;只需不生成任何链文件)。这与当前版本中可链化检测结果少于两个时的行为一致。
- 读取Markdown知识库(和
workspace/kb/entities/),识别本身可能不是漏洞但可作为跳板的架构原语(例如,"用户控制文件上传路径"、"服务以root身份运行")。workspace/kb/vulnerabilities/
- 读取
-
跨检测结果分析(链构建矩阵):
- 分析每个已验证检测结果的前置条件和后置条件。
- 思考:检测结果A的输出或副作用是否能满足触发检测结果B所需的严格前置条件?
- 需要寻找的示例链:
- 路径遍历 + 宽松权限 = 远程代码执行(RCE): 低严重性路径遍历(检测结果A)允许写入,而单独的配置错误(检测结果B)允许cron作业执行
/tmp中的脚本。/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,则回退到有序的组成UUID序列(当前行为)。只有当匹配满足决胜条件时,才跳过此链(不创建文件)并处理下一个候选链。signature - 相反,生成一个全新的UUID并在中创建新的检测结果JSON文件。
workspace/findings/<new_uuid>.json - 组成检测结果: 必须在结构化的属性中记录组成检测结果的UUID数组(例如,
"constituent_findings")。这将清晰记录漏洞利用链的各个环节。["UUID_A", "UUID_B"] - 确定发现快照(): 根据步骤1中保留的合格组成项设置链检测结果的
discovery_commit:discovery_commit- MODE-OFF模式短路(三态规则): 如果状态中不存在(MODE-OFF模式——未请求
active_snapshot),则完全省略链检测结果的--sync。在MODE-OFF模式下,研究人员会省略每个组成项的discovery_commit(discovery_commit:"如果active_snapshot不存在或snapshot_pinned为false,则完全省略此键"),因此每个组成项的researcher:317,340-341均缺失/为空。下面的相同/混合逻辑会在每个链中写入字面discovery_commit——这是快照时代的产物,在第一阶段不存在,且违反了MODE-OFF模式的"与当前版本字节级一致"保证("MIXED")。链自身的架构注释(schema.json:4)指出"在旧版/未固定扫描中省略",确认MODE-OFF模式的预期行为是省略而非chain:307。(功能影响很小——"MIXED"将"缺失"和schema.json:232均视为NOT_MATCHED——但模式一致性很重要。)在MODE-OFF模式下请勿写入"MIXED"或任何值。"MIXED" - 如果所有合格组成项的值相同且非空,则将链的
discovery_commit设置为该精确字符串。discovery_commit - 如果组成项的值彼此不同,或任一合格组成项的
discovery_commit缺失/为空(仅在PINNED/HALT模式下可达——在MODE-OFF模式下,上述短路已省略该字段),则将链的discovery_commit设置为字面字符串discovery_commit。请勿发明、哈希或模糊标准化此值——复制精确字符串或写入字面"MIXED"。理由:在固定模式下,每个合格组成项均为MATCHED,因此它们都共享当前"MIXED",链将继承该值(之后对链运行模块B会在本次扫描中返回MATCHED)。SNAPSHOT_ID标记会使模块B对链返回NOT_MATCHED(模块B步骤2将字面"MIXED"视为NOT_MATCHED)——这是针对无法针对单个快照验证环节的链的安全分支。"MIXED"
- MODE-OFF模式短路(三态规则): 如果状态中不存在
- 计算(链的确定性内容身份哈希):
signature- 收集每个合格组成项(步骤1中用于设置的同一集合)的
discovery_commit字段。signature - 如果每个合格组成项都有非空的:按字典序(升序)对组成项签名排序,然后计算
signature=signature的前16个十六进制字符。sha256("chain|" + "|".join(sorted_constituent_signatures))前缀可防止与非链检测结果的"chain|"发生冲突(后者是根据标题+CWE+目标哈希生成的,输入域不同)。排序使链签名不受组成项顺序影响。signature - 如果任一合格组成项缺少字段(旧版/未升级的检测结果):将链的
signature设置为空字符串并从JSON中省略(下游消费者会根据signature回退到基于UUID的合并/去重)。这是安全分支——绝不会导致错误的合并,只会过度报告。schema.json:256 - 在链创建时计算一次签名,绝不重新计算、编辑或伪造(与以及常规检测结果的
discovery_commit规则相同)。researcher:241-242
- 收集每个合格组成项(步骤1中用于设置
- 计算(跨扫描次数的链谱系):
lineage_id- 扫描和旧版
workspace/archive/findings_pass_*/,寻找workspace/archive/loop*_findings/字段与当前链计算出的signature完全匹配的归档链检测结果JSON(任何包含非空signature数组的检测结果)。constituent_findings - 如果找到匹配项:= 归档祖先的
lineage_id(继承谱系链,以便报告可跨扫描次数合并)。如果多个归档链共享相同的lineage_id,则从最新(扫描次数最高)的祖先继承。所有具有相同链signature的祖先应共享相同的signature;如果不共享,则从最新的祖先继承并记录警告。lineage_id - 如果未找到匹配项(包括当前链的为空字符串的情况):
signature= 新的UUIDv4。lineage_id - 这些是状态相对路径(模块A步骤3)——在下读取,绝不在CODE_ROOT下读取。(与常规检测结果的
--state_root/workspace/archive/具有相同的扫描目标和算法结构——链只需跳过基于基名标准化的重命名回退,因为链的身份由其组成项集合决定,而非单个文件路径。)researcher:246-272
- 扫描
- 确定入口点权限: 链的字段必须表示启动链第一步(入口点)所需的权限级别。例如,如果链以未认证漏洞(NONE)开始,进而获得管理员权限,然后用于触发RCE,则链的
privileges_required必须设置为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"
- 如果任一组成项的
链检测结果架构格式(每个文件)
json{ "id": "为该链检测结果生成的唯一标识符。必须与文件名匹配。", "title": "Exploit Chain: [Impact] via [Finding A] and [Finding B]", "description": "漏洞利用链的分步文档。从步骤1(触发检测结果A)开始,解释其结果如何传入步骤N(触发检测结果Z)。", "impact": "链的组合升级影响(例如,Remote Code Execution、Full Database Exfiltration)。该影响应高于单个检测结果的影响。", "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": "打破链的推荐策略。通常需要修复至少一个(如果不是全部)底层环节。", "status": "VALID", "production_viability": "VIABLE / SAMPLE_OR_TEST / CONDITIONAL_VIABLE", "repro_status": "statically_confirmed / not_attempted", "constituent_findings": ["UUID_A", "UUID_B"], "signature": "如果每个组成项都有非空的`signature`,则为sha256(\"chain|\" + \"|\".join(sorted(constituent_signatures)))的前16个十六进制字符;否则为空字符串(等效于缺失 -> 下游回退到仅基于UUID的行为)。在链创建时计算一次,绝不重新计算(与discovery_commit规则相同)。详见上述签名/谱系计算步骤。", "lineage_id": "从`signature`与当前链`signature`完全匹配的最新归档链检测结果继承(扫描workspace/archive/findings_pass_*/和workspace/archive/loop*_findings/);否则为新的UUIDv4。详见上述签名/谱系计算步骤。", "discovery_commit": "所有组成项共享的SNAPSHOT_ID,或如果它们不同/缺失则为字面\"MIXED\"(仅在PINNED/HALT模式下计算)。在旧版/未固定扫描(MODE-OFF)中省略(缺失)——详见上述discovery_commit计算步骤中的MODE-OFF短路说明。", "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"字段中显著包含"Chain"一词,如架构所示。"history"
- 为确保
确保所有新构建的链文件都写入目录。完成后通知用户。
workspace/findings/