mantis-researcher

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Mantis Researcher (/mantis-researcher)

Mantis Researcher(/mantis-researcher)

System Goal

系统目标

Resilience Code Auditor. Performs rapid triage and deep-dive reviews of source files to identify boundary checks, preconditions, missing sanitization, and interface violations.
韧性代码审计工具。对源文件执行快速分类和深度评审,以识别边界检查、前置条件、缺失的 sanitization 以及接口违规问题。

Command Definition

命令定义

  • Command:
    /mantis-researcher
  • Description: Audits production source code files based on the strategy in
    workspace/plan.json
    .
  • Arguments (optional; supplied by the orchestrator, consumed by Block A):
    • --snapshot_root
      /
      SNAPSHOT_ROOT
      : absolute path to the pinned, read-only code snapshot for this pass. This is the CODE_ROOT that all snapshot-relative path fields resolve against (Block A step 1b).
    • --snapshot_id
      /
      SNAPSHOT_ID
      : the pass snapshot identifier. Used for the sentinel check (Block A step 2) and stamped verbatim into every finding's
      discovery_commit
      .
    • --state_root
      : absolute path to the
      workspace/
      state directory (
      plan.json
      ,
      .mantis_state.json
      ,
      findings/
      ,
      kb/
      ). State paths are STATE-RELATIVE and are NEVER prefixed with CODE_ROOT (Block A step 3).
    • --target_root
      (authoritative override, Block A step 1a) is also honored if supplied.
    • All flags absent -> DEGRADED/legacy mode: CODE_ROOT falls back to the current directory,
      snapshot_pinned
      is treated as false, and behavior matches today's exactly (no
      discovery_commit
      is written).
  • 命令:
    /mantis-researcher
  • 描述: 基于
    workspace/plan.json
    中的策略审计生产源代码文件。
  • 参数(可选;由编排器提供,供A模块使用):
    • --snapshot_root
      /
      SNAPSHOT_ROOT
      :本次审计所用的固定只读代码快照的绝对路径。这是所有快照相对路径字段的解析根目录(见A模块第1b步)。
    • --snapshot_id
      /
      SNAPSHOT_ID
      :本次审计的快照标识符。用于哨兵检查(A模块第2步),并原样标记到每个发现结果的
      discovery_commit
      字段中。
    • --state_root
      workspace/
      状态目录的绝对路径(包含
      plan.json
      .mantis_state.json
      findings/
      kb/
      )。状态路径为状态相对路径,绝对不能以CODE_ROOT作为前缀(见A模块第3步)。
    • 如果提供
      --target_root
      (权威覆盖,A模块第1a步),也会生效。
    • 所有标志缺失 -> 降级/遗留模式: CODE_ROOT回退到当前目录,
      snapshot_pinned
      视为false,行为与当前完全一致(不会写入
      discovery_commit
      )。

Input/Output Contract

输入/输出约定

  • Reads:
    • workspace/plan.json
      (falls back to codebase sweep if missing/empty).
    • workspace/.mantis_state.json
      (to track current loop pass).
    • referenced Markdown files in
      "kb_references"
      (e.g.
      workspace/kb/entities/*.md
      ).
    • Target source code files.
    • workspace/kb/structural_index/manifest.json
      (to check structural index availability/status).
    • workspace/helpers/query_structural_index.py
      (to invoke bounded structural-index queries).
  • Writes:
    • Raw finding files to
      workspace/findings/<uuid>.json
      (creates
      workspace/findings/
      if missing).
  • Preconditions:
    • Target files must be accessible.
  • Idempotency Guarantee:
    • Writes new findings as separate files with unique UUIDs. Rely on
      mantis-dedupe
      to cluster and merge duplicate findings on subsequent steps.
  • 读取:
    • workspace/plan.json
      (若缺失/为空则回退到代码库全量扫描)。
    • workspace/.mantis_state.json
      (用于跟踪当前循环审计次数)。
    • "kb_references"
      中引用的Markdown文件(例如
      workspace/kb/entities/*.md
      )。
    • 目标源代码文件。
    • workspace/kb/structural_index/manifest.json
      (用于检查结构索引的可用性/状态)。
    • workspace/helpers/query_structural_index.py
      (用于调用有限范围的结构索引查询)。
  • 写入:
    • 将原始发现结果写入
      workspace/findings/<uuid>.json
      (若
      workspace/findings/
      目录不存在则自动创建)。
  • 前置条件:
    • 目标文件必须可访问。
  • 幂等性保证:
    • 将新发现结果写入带有唯一UUID的独立文件。后续步骤依赖
      mantis-dedupe
      来聚类和合并重复的发现结果。

Instructions

操作说明

Step 0: Locator Resolution (Snapshot-Aware Path Handling)

步骤0:定位器解析(快照感知路径处理)

Run this BEFORE the numbered research steps below. It fixes the single CODE_ROOT that every
target_files
/
code_paths
reference in this stage resolves against, so all sub-agents audit the same pinned snapshot.
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.
Skill-specific notes for the researcher:
  • The researcher is a CODE-READING stage, so Block A step 0's findings-only skip does NOT apply here — you MUST resolve CODE_ROOT and honor the sentinel.
  • workspace/plan.json
    target_files
    and finding
    code_paths
    are SNAPSHOT-RELATIVE: resolve them under CODE_ROOT (Block A step 3).
  • kb_references
    ,
    workspace/plan.json
    ,
    workspace/.mantis_state.json
    , and everything under
    workspace/findings/
    are STATE-RELATIVE: read/write them under --state_root, NEVER under CODE_ROOT.
  • Never write, compile, or generate anything under CODE_ROOT when pinned (Block A step 4).
Perform a thorough memory-safety, logical-correctness, and robustness review of the targeted codebase.
Execute the research stage as follows:
  1. Load Reviewing Plan & Context: Read the active pass number from
    workspace/.mantis_state.json
    and resolve the current ISO 8601 timestamp. Read the
    workspace/plan.json
    file to retrieve the target investigations. If
    workspace/plan.json
    is missing or empty, perform a general list of the directories and review any primary source files. If the investigation contains a
    "kb_references"
    array, explicitly read those Markdown files (e.g.,
    workspace/kb/entities/auth.md
    ) to gain compounded historical context before you begin auditing the
    "target_files"
    . Also read
    active_snapshot
    from
    workspace/.mantis_state.json
    (
    root
    ,
    snapshot_id
    ,
    snapshot_pinned
    ). Hold
    active_snapshot.snapshot_id
    in memory: it is the value you will stamp into every finding's
    discovery_commit
    (see Findings Schema Format). If
    active_snapshot
    is absent or
    snapshot_pinned
    is false, you are in DEGRADED/legacy mode — do NOT stop (Block A step 1d); you will simply omit
    discovery_commit
    .
  2. Sub-Agent Delegation (Wave-Based Swarm Parallelization): If the CLI or agent platform supports spawning sub-agents (e.g., using specialized sub-agent tools or multi-agent orchestrator directives):
    • Do not execute investigations sequentially if sub-agents are supported. Split the investigations in
      workspace/plan.json
      into parallel waves to maximize throughput and context efficiency.
    • Wave 1: Lightweight Rapid Triage (Concurrency Peak): Spawn concurrent, lightweight sub-agents (e.g. up to 10-20 in parallel) to sweep all files listed in
      workspace/plan.json
      . Each sub-agent should only output a fast classification:
      {"potentially_flawed": true/false, "reason": "..."}
      .
    • Wave 2: Deep Security Flaw Hotspot Audits & Parallel Trajectory Search: Collect all files flagged in Wave 1. Spawn a wave of concurrent deep auditor sub-agents (e.g. up to 4-8 in parallel) to focus exclusively on those identified hotspots. For particularly complex files, spawn multiple subagents targeting the same file using either different prompt constraints or a diverse set of less expensive LLMs to explore parallel attack vectors. Rely on the subsequent deduplication stage to merge any overlapping findings.
    • Token Optimization (Distributed Writes): Instruct the Wave 2 sub-agents to generate unique UUIDs and write their findings directly to individual
      workspace/findings/<id>.json
      files on disk. Do not ask them to return the full JSON payload in their messages back to you, as aggregating them will blow out your context window. Ask them to only return the list of UUIDs they created.
    • Snapshot Isolation (Wave Pinning) — MANDATORY: Pass the SAME
      --snapshot_root
      (CODE_ROOT resolved in Step 0) and the same
      --snapshot_id
      value to EVERY Wave-1 and Wave-2 sub-agent, and instruct each of them to obey Block A (resolve
      target_files
      /
      code_paths
      under that CODE_ROOT, honor the sentinel). Any Wave-2 sub-agent that writes a finding MUST stamp
      discovery_commit
      with that snapshot_id, exactly as specified in the Findings Schema Format. Sub-agents MUST NOT run
      git pull
      /
      fetch
      /
      checkout
      /
      reset
      ,
      hg pull
      /
      update
      ,
      repo sync
      , or any command that changes the working tree or switches revisions — the snapshot is immutable for the whole pass. A sub-agent that cannot see the snapshot must report that, not re-sync.
    • If sub-agents or concurrency are not supported by the current environment, fall back to performing the sweeps and deep-dives sequentially.
    • Structural Index (HINT-only enhancement): When a structural index is available (
      workspace/kb/structural_index/manifest.json
      exists), use it to SUPPLEMENT the wave-based swarm above. The structural index decides ORDER, never MEMBERSHIP. It MUST NEVER replace the exhaustive Step-3 call-site sweep.
      • Resolution-first protocol (MANDATORY before any structural query):
        1. Resolve the symbol first:
          python3 workspace/helpers/query_structural_index.py resolve_symbol --name "<function_name>" [--language "<lang>"] [--file "<path>"] --state_root <state_root>
        2. If the response has
          ambiguous: true
          , investigate ALL matched symbols — never silently pick one. Narrow with
          --file
          /
          --language
          if possible, or schedule investigations for every matched symbol.
        3. Use the resolved
          symbol_id
          for bounded queries:
          python3 workspace/helpers/query_structural_index.py find_callers --symbol_id "<id>" --limit 100 --offset 0 --state_root <state_root>
      • Coverage-aware interpretation: Check
        coverage.partition_status
        in every structural index response:
        • complete
          +
          precision == semantic
          + empty results = "no indexed callers" (authoritative for indexed code — still run grep per the HINT-only rule).
        • complete
          +
          precision != semantic
          + empty results = "no indexed callers" — NOT authoritative. MUST run exhaustive grep.
        • partial
          /
          empty
          /
          failed
          + empty results = "not fully indexed" — MUST run exhaustive grep.
        • Use the
          precision
          and
          backend
          fields on every result to weight trust (
          semantic
          >
          typecheck
          >
          ast
          >
          symbol-only
          >
          heuristic
          >
          deferred
          >
          coverage-only
          ).
      • Wave 1 (Rapid Triage): Use
        find_callers()
        to SUPPLEMENT grep as a ranking HINT — ORDER, never MEMBERSHIP. Structural index results prioritize which files to flag as
        potentially_flawed
        ; they MUST NEVER replace the exhaustive Step-3 call-site sweep. Audit the union of grep results and structural index results.
      • Wave 2 (Deep Audit): Use
        get_function_boundary(file, line)
        to start with the enclosing function, then expand to callers/callees/file as needed for deep dive context.
      • Graceful degradation: If the structural index is absent (no
        manifest.json
        ), empty, or the query helper is missing, fall back to grep-based discovery (today's behavior). The structural index is a coverage HINT only.
  3. Exhaustive Interface and Call-Site Reviewing: If a target source file defines public or API functions (such as numeric parsers, decoders, encoders, or converters) that document explicit size constraints or safety requirements (e.g., expecting callers to allocate buffers of a certain size):
    • Run a repo-wide grep for the function name to build the exhaustive set of candidate call-sites — this is the mandatory floor. Then use the structural index query helper (
      resolve_symbol
      then
      find_callers
      ) to RANK and prioritize which call-sites to audit first (the index distinguishes actual calls from comments/strings/variable names). Audit the union of both result sets — the structural index may miss macro-based calls, function pointers, and dynamic dispatch, so grep remains the floor.
    • Search the codebase to find and review all call-sites of these functions across the entire repository to ensure the safety contracts are respected globally.
    • Read the calling files and verify if every call-site strictly adheres to input constraints, properly manages bounds, and checks sizes.
    • Flag any discrepancies as contract alignment bugs or missing checks.
  4. Unconstrained / Exploratory Investigations: If the investigation plan in
    workspace/plan.json
    contains instructions or a question explicitly asking for an unconstrained sweep, adversarial audit, or random exploration:
    • Ignore existing assumptions of safety and documented trust boundaries in
      workspace/kb/THREAT_MODEL.md
      .
    • Treat all inputs and boundaries as untrusted and potentially malformed.
    • Analyze implementation from scratch with full freedom and autonomy.
    • If it is a random exploration/digging task with minimal instructions, focus on mapping the behavior of the target files, identifying key entry points, and looking for unexpected side effects or boundary cases without being constrained by a specific threat model.
  5. Compile and Write Findings: Instead of a single monolithic file, create a
    workspace/findings/
    directory if it does not exist. For each potential finding, generate a unique UUID and write a valid JSON object into an individual file named
    workspace/findings/<id>.json
    . This keeps findings isolated and prevents token limit issues during subsequent analysis. Do not include any text before or after the JSON in the files.
    Signature & Lineage Computation (Step 5a):
    For each finding, compute
    signature
    ,
    lineage_id
    , and optionally
    cwe
    BEFORE writing the JSON file:
    1. cwe
      (optional):
      If the vulnerability maps to a known CWE, set the
      cwe
      field to the identifier (e.g.,
      "CWE-787"
      ,
      "CWE-416"
      ). If no CWE applies, omit the field. This is an input to the signature.
    2. Compute
      signature
      (deterministic content-identity hash):
      • normalized_title
        = finding
        title
        , lowercased, with ALL non-alphanumeric characters stripped (ASCII
        [a-zA-Z0-9]
        only; all other characters including Unicode letters, punctuation, and spaces are removed). If stripping leaves
        normalized_title
        EMPTY (e.g. a title composed entirely of non-ASCII/Unicode characters), set
        normalized_title
        = the first 16 hex chars of
        sha256(<original raw title as UTF-8 bytes>)
        so two distinct non-ASCII titles do not collide on the empty string.
      • cwe_part
        = the finding's
        cwe
        field if present and non-null, else the empty string.
      • primary_target
        = first
        code_paths
        entry with trailing
        :line
        stripped (e.g.,
        src/auth.c:145
        src/auth.c
        ). If
        code_paths
        is empty, or the first entry is a non-source LOCATOR (URL containing
        ://
        , or a non-file symbol/offset per Block A step 3), use the empty string. Order
        code_paths
        deterministically (primary sink first, kept stable across passes) so
        primary_target
        — and thus the
        signature
        — does not drift between passes. (If the order is unstable the only cost is a missed lineage inheritance → the finding is over-reported as new, never hidden — but stable order preserves the cross-pass fold.)
      • If
        primary_target
        is non-empty:
        signature
        = first 16 hex characters of
        sha256(normalized_title + "|" + cwe_part + "|" + primary_target)
        .
      • If
        primary_target
        is empty:
        signature
        = first 16 hex characters of
        sha256(normalized_title + "|" + cwe_part + "|" + sorted(code_paths).join(","))
        .
      • Compute the signature ONCE at finding creation and NEVER recompute, edit, or invent it (same rule as
        discovery_commit
        ).
    3. Compute
      lineage_id
      (cross-pass lineage chain):
      • Scan
        workspace/archive/findings_pass_*/
        and
        workspace/archive/loop*_findings/
        for any archived finding JSON whose
        signature
        field equals this finding's computed
        signature
        .
      • If a match is found:
        lineage_id
        = the archived ancestor's
        lineage_id
        (inherit the lineage chain so consumers can fold across passes). If MULTIPLE archived findings share the same signature, inherit from the MOST RECENT (highest pass number) ancestor. All ancestors with the same signature SHOULD share the same lineage_id; if they don't, inherit from the most recent one and log a warning.
      • If no match by exact signature: attempt a basename rename fallback for TRUE renames ONLY. Do NOT recompute the signature (it is computed ONCE at creation and never recomputed — invariant #4). Instead:
        1. Compute the CURRENT finding's basename: take
          primary_target
          (the first
          code_paths
          entry with trailing
          :line
          stripped, already computed for the signature at step 2) and take its basename (e.g.
          src/auth.c
          auth.c
          ). If
          primary_target
          is empty (non-source LOCATOR or empty code_paths), SKIP this fallback — go to fresh UUIDv4 below.
        2. For each archived finding located in step 3's archive scan: reconstruct the ARCHIVED finding's basename from its stored
          code_paths[0]
          (strip the trailing
          :line
          , take the basename — e.g.
          lib/old_auth.c:88
          old_auth.c
          ). Do NOT recompute the archived finding's signature and do NOT compare signatures; compare the two basenames as STRINGS.
        3. Inherit an ancestor's
          lineage_id
          via this fallback ONLY IF (i) the basenames match AND (ii) the ancestor's full
          primary_target
          (its
          code_paths[0]
          with
          :line
          stripped) NO LONGER EXISTS on the current snapshot (check that the old full path is absent under CODE_ROOT — this distinguishes a real rename from a second, independent file that merely shares a basename). If MULTIPLE archived ancestors satisfy (i) and (ii), inherit from the MOST RECENT (highest pass number). When you inherit via basename, ALSO add a finding history note
          lineage-via-basename-rename
          so downstream consumers treat the link as basename-derived (the report folds two findings only when their full
          signature
          s ALSO match per the SAME-BUG predicate, so a basename-derived lineage link never collapses distinct bugs).
        4. If the old full path still exists on the current snapshot, do NOT inherit — treat as no match (fresh UUIDv4 below).
      • If no match by either exact signature or basename rename:
        lineage_id
        = a fresh UUIDv4.
      • These are STATE-RELATIVE paths (Block A step 3) — read under
        --state_root/workspace/archive/
        , NEVER under CODE_ROOT.
    4. Write all three fields (
      cwe
      ,
      signature
      ,
      lineage_id
      ) into the finding JSON alongside
      discovery_commit
      .
    Mode-independence: Unlike
    discovery_commit
    (which is omitted in DEGRADED/legacy mode),
    signature
    and
    lineage_id
    are ALWAYS computed — they are content-identity fields, not snapshot-identity. They work regardless of whether the snapshot is pinned, unpinned, or absent (MODE-OFF). This is a deliberate Phase-3 improvement: in MODE-OFF, the finding JSON now carries 2–3 extra optional keys (
    cwe
    ,
    signature
    ,
    lineage_id
    ) that did not exist in Phase 1. This does NOT change the snapshot model (no
    active_snapshot
    , no sync, no pin — the 3-state rule is unaffected). The dedupe MODE-OFF fallback now prefers
    signature
    over
    stable_key
    when present, which is strictly MORE discriminating (signature adds
    cwe
    to the title+path mix), so it can only SPLIT entries that
    stable_key
    would have merged (fresh budgets, more conservative = safe) — it can never produce a false merge or suppress a legitimate finding. Absent
    signature
    → today's
    stable_key
    behavior exactly.
    Missing or unreadable target file: If a path in
    target_files
    does not exist or cannot be read under CODE_ROOT (e.g. it was deleted or renamed since the plan was written), do NOT fabricate a finding, a line number, or file contents. Skip that target and record the skip plainly in the
    description
    of any finding it relates to (or omit it entirely). Never invent code you did not read.
    Non-source targets: For a non-source LOCATOR (binary, firmware, or a URL endpoint — see Block A step 3), the
    code_paths
    entry MUST be a STABLE LOCATOR (symbol name, offset, or the bare path) WITHOUT a
    :line
    suffix. Do not attach a fabricated line number to a target you cannot open as text.
在执行以下编号的研究步骤之前先运行此步骤。它会确定一个统一的CODE_ROOT,此阶段中所有
target_files
/
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(模式关闭)。请勿停止。
2. 哨兵检查(仅当snapshot_pinned为true且未采用路径1a时执行):
   验证CODE_ROOT/.mantis_snapshot_id存在且等于SNAPSHOT_ID。若缺失或不匹配 -> 停止并提示"snapshot sentinel mismatch"。(--target_root对应的代码树(1a)是故意修改的,无需进行哨兵检查。)
3. 路径字段:
   - 快照相对路径(在CODE_ROOT下读取):code_paths条目;plan中的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. VCS元数据例外:历史日志提取以及任何VCS diff/blame命令必须在实时仓库根目录(包含.git/.hg/.repo)中运行,而非CODE_ROOT(快照副本会剥离VCS元数据)。绝不能仅因CODE_ROOT缺少.git/.hg/.repo而停止。
6. 所有shell命令使用绝对路径,并在调用时设置自己的工作目录。请勿假设工作目录在调用之间保持不变。
针对Researcher的技能特定说明:
  • Researcher属于代码读取阶段,因此A模块步骤0中仅处理发现结果的跳过规则不适用——必须解析CODE_ROOT并遵守哨兵检查。
  • workspace/plan.json
    中的
    target_files
    和发现结果的
    code_paths
    均为快照相对路径:需在CODE_ROOT下解析(见A模块第3步)。
  • kb_references
    workspace/plan.json
    workspace/.mantis_state.json
    以及
    workspace/findings/
    下的所有内容均为状态相对路径:需在--state_root下读取/写入,绝不能在CODE_ROOT下操作。
  • 当快照固定时,绝不能在CODE_ROOT下写入、编译或生成任何内容(见A模块第4步)。
对目标代码库进行全面的内存安全、逻辑正确性和健壮性评审。
按以下步骤执行研究阶段:
  1. 加载评审计划与上下文:
    workspace/.mantis_state.json
    读取当前审计次数,并解析当前ISO 8601时间戳。读取
    workspace/plan.json
    文件以获取目标调查内容。如果
    workspace/plan.json
    缺失或为空,则列出所有目录并评审主要源代码文件。如果调查内容包含
    "kb_references"
    数组,在开始审计
    "target_files"
    之前,需明确读取这些Markdown文件(例如
    workspace/kb/entities/auth.md
    )以获取综合历史上下文。同时从
    workspace/.mantis_state.json
    读取
    active_snapshot
    root
    snapshot_id
    snapshot_pinned
    )。将
    active_snapshot.snapshot_id
    保存在内存中:该值将标记到每个发现结果的
    discovery_commit
    字段中(见发现结果 Schema 格式)。如果
    active_snapshot
    缺失或
    snapshot_pinned
    为false,则处于降级/遗留模式——请勿停止(见A模块第1d步);只需省略
    discovery_commit
    字段即可。
  2. 子代理委派(基于波次的集群并行化): 如果CLI或代理平台支持生成子代理(例如使用专用子代理工具或多代理编排指令):
    • 如果支持子代理,请勿按顺序执行调查任务。将
      workspace/plan.json
      中的调查任务拆分为并行波次,以最大化吞吐量和上下文效率。
    • 波次1:轻量级快速分类(并发峰值): 生成并发的轻量级子代理(例如最多10-20个并行),扫描
      workspace/plan.json
      中列出的所有文件。每个子代理只需输出快速分类结果:
      {"potentially_flawed": true/false, "reason": "..."}
    • 波次2:深度安全漏洞热点审计与并行轨迹搜索: 收集波次1中标记的所有文件。生成一波并发的深度审计子代理(例如最多4-8个并行),专门聚焦于这些已识别的热点。对于特别复杂的文件,生成多个子代理针对同一文件,使用不同的提示约束或多样化的低成本LLM来探索并行攻击向量。后续的去重阶段会合并重叠的发现结果。
    • Token优化(分布式写入): 指示波次2的子代理生成唯一UUID,并将发现结果直接写入磁盘上的独立
      workspace/findings/<id>.json
      文件。不要让它们将完整的JSON负载返回给你,否则会超出上下文窗口。只需让它们返回创建的UUID列表即可。
    • 快照隔离(波次固定)——强制要求: 将相同的
      --snapshot_root
      (步骤0中解析的CODE_ROOT)和相同的
      --snapshot_id
      值传递给每个波次1和波次2的子代理,并指示它们遵守A模块规则(在该CODE_ROOT下解析
      target_files
      /
      code_paths
      ,遵守哨兵检查)。任何写入发现结果的波次2子代理必须将
      discovery_commit
      标记为该snapshot_id,完全符合发现结果Schema格式的要求。子代理绝不能运行
      git pull
      /
      fetch
      /
      checkout
      /
      reset
      hg pull
      /
      update
      repo sync
      或任何会更改工作树或切换版本的命令——快照在整个审计过程中是不可变的。无法访问快照的子代理必须报告该情况,而非重新同步。
    • 如果当前环境不支持子代理或并发,则回退到按顺序执行扫描和深度评审。
    • 结构索引(仅作为提示的增强功能): 当结构索引可用时(
      workspace/kb/structural_index/manifest.json
      存在),使用它来补充上述基于波次的集群策略。结构索引仅决定顺序,绝不决定范围。绝不能用它替代步骤3中的详尽调用点扫描。
      • 解析优先协议(任何结构查询之前必须执行):
        1. 先解析符号:
          python3 workspace/helpers/query_structural_index.py resolve_symbol --name "<function_name>" [--language "<lang>"] [--file "<path>"] --state_root <state_root>
        2. 如果响应中
          ambiguous: true
          ,则必须调查所有匹配的符号——绝不能静默选择其中一个。尽可能使用
          --file
          /
          --language
          缩小范围,或为每个匹配的符号安排调查任务。
        3. 使用解析后的
          symbol_id
          进行有限范围查询:
          python3 workspace/helpers/query_structural_index.py find_callers --symbol_id "<id>" --limit 100 --offset 0 --state_root <state_root>
      • 覆盖范围感知解读: 检查每个结构索引响应中的
        coverage.partition_status
        • complete
          +
          precision == semantic
          + 空结果 = "无已索引的调用者"(对已索引代码具有权威性——仍需按仅提示规则运行grep)。
        • complete
          +
          precision != semantic
          + 空结果 = "无已索引的调用者"——不具有权威性。必须运行详尽的grep。
        • partial
          /
          empty
          /
          failed
          + 空结果 = "未完全索引"——必须运行详尽的grep。
        • 使用每个结果中的
          precision
          backend
          字段来加权信任度(
          semantic
          >
          typecheck
          >
          ast
          >
          symbol-only
          >
          heuristic
          >
          deferred
          >
          coverage-only
          )。
      • 波次1(快速分类): 使用
        find_callers()
        作为排名提示来补充grep——仅决定顺序,绝不决定范围。结构索引结果优先标记哪些文件为
        potentially_flawed
        ;绝不能用它替代步骤3中的详尽调用点扫描。审计grep结果和结构索引结果的并集。
      • 波次2(深度审计): 使用
        get_function_boundary(file, line)
        从包含的函数开始,根据深度评审上下文的需要扩展到调用者/被调用者/文件。
      • 优雅降级: 如果结构索引缺失(无
        manifest.json
        )、为空或查询辅助工具缺失,则回退到基于grep的发现方式(当前行为)。结构索引仅作为覆盖范围提示。
  3. 详尽的接口与调用点评审: 如果目标源文件定义了公共或API函数(例如数值解析器、解码器、编码器或转换器),且这些函数文档明确说明了大小约束或安全要求(例如要求调用者分配特定大小的缓冲区):
    • 运行全仓库范围的grep搜索函数名称,构建详尽的候选调用点集合——这是强制要求的基础范围。然后使用结构索引查询辅助工具(先
      resolve_symbol
      find_callers
      )对调用点进行排名和优先评审(索引可区分实际调用与注释/字符串/变量名)。审计两个结果集的并集——结构索引可能会遗漏基于宏的调用、函数指针和动态调度,因此grep仍是基础范围。
    • 搜索代码库,找到并评审这些函数在整个仓库中的所有调用点,确保安全契约在全局范围内得到遵守。
    • 读取调用文件,验证每个调用点是否严格遵守输入约束、正确管理边界并检查大小。
    • 将任何不一致标记为契约对齐错误或缺失检查。
  4. 无约束/探索性调查: 如果
    workspace/plan.json
    中的调查计划包含明确要求无约束扫描、对抗性审计或随机探索的指令或问题:
    • 忽略
      workspace/kb/THREAT_MODEL.md
      中现有的安全假设和已记录的信任边界。
    • 将所有输入和边界视为不可信且可能存在畸形。
    • 从头开始分析实现,拥有完全的自由和自主权。
    • 如果是指令极少的随机探索/挖掘任务,则重点关注目标文件的行为映射、识别关键入口点,并寻找意外的副作用或边界情况,不受特定威胁模型的约束。
  5. 编译并写入发现结果: 不要创建单个整体文件,若
    workspace/findings/
    目录不存在则创建该目录。对于每个潜在的发现结果,生成唯一UUID并将有效的JSON对象写入名为
    workspace/findings/<id>.json
    的独立文件中。这样可以隔离发现结果,避免后续分析中的Token限制问题。文件中除JSON外不要包含任何其他文本。
    签名与谱系计算(步骤5a):
    在写入JSON文件之前,为每个发现结果计算
    signature
    lineage_id
    以及可选的
    cwe
    1. cwe
      (可选):
      如果漏洞对应已知的CWE,则将
      cwe
      字段设置为标识符(例如
      "CWE-787"
      "CWE-416"
      )。如果无适用的CWE,则省略该字段。这是签名的输入项。
    2. 计算
      signature
      (确定性内容身份哈希):
      • normalized_title
        = 发现结果的
        title
        ,转换为小写,并去除所有非字母数字字符(仅保留ASCII
        [a-zA-Z0-9]
        ;所有其他字符包括Unicode字母、标点符号和空格均移除)。如果去除后
        normalized_title
        为空(例如标题完全由非ASCII/Unicode字符组成),则将
        normalized_title
        设置为
        sha256(<原始原始标题的UTF-8字节>)
        的前16个十六进制字符,以避免两个不同的非ASCII标题因空字符串而冲突。
      • cwe_part
        = 发现结果的
        cwe
        字段(若存在且非空),否则为空字符串。
      • primary_target
        = 第一个
        code_paths
        条目,去除末尾的
        :line
        (例如
        src/auth.c:145
        src/auth.c
        )。如果
        code_paths
        为空,或第一个条目是非源代码定位器(包含
        ://
        的URL,或符合A模块第3步的非文件符号/偏移量),则使用空字符串。确定性排序
        code_paths
        (主 sink 优先,在审计过程中保持稳定),确保
        primary_target
        ——进而
        signature
        ——在不同审计过程中不会变化。(如果顺序不稳定,唯一的代价是丢失谱系继承→发现结果会被重复报告为新结果,绝不会被隐藏——但稳定顺序可保留跨审计过程的合并。)
      • 如果
        primary_target
        非空:
        signature
        =
        sha256(normalized_title + "|" + cwe_part + "|" + primary_target)
        的前16个十六进制字符。
      • 如果
        primary_target
        为空:
        signature
        =
        sha256(normalized_title + "|" + cwe_part + "|" + sorted(code_paths).join(","))
        的前16个十六进制字符。
      • 在发现结果创建时计算一次签名,绝不重新计算、编辑或伪造(与
        discovery_commit
        规则相同)。
    3. 计算
      lineage_id
      (跨审计过程的谱系链):
      • 扫描
        workspace/archive/findings_pass_*/
        workspace/archive/loop*_findings/
        ,查找任何存档的发现结果JSON,其
        signature
        字段等于当前发现结果计算出的
        signature
      • 如果找到匹配项:
        lineage_id
        = 存档祖先的
        lineage_id
        (继承谱系链,以便使用者跨审计过程合并结果)。如果多个存档发现结果共享相同的签名,则继承最新(审计次数最高)的祖先的
        lineage_id
        。所有具有相同签名的祖先应共享相同的lineage_id;如果不共享,则继承最新的祖先并记录警告。
      • 如果没有完全匹配的签名:仅针对真实重命名尝试basename重命名回退。绝不要重新计算签名(签名在创建时计算一次,绝不重新计算——不变规则#4)。而是:
        1. 计算当前发现结果的basename:取
          primary_target
          (已为签名计算的第一个
          code_paths
          条目,去除末尾的
          :line
          )并获取其basename(例如
          src/auth.c
          auth.c
          )。如果
          primary_target
          为空(非源代码定位器或空code_paths),则跳过此回退——使用下面的新UUIDv4。
        2. 对于步骤3存档扫描中找到的每个存档发现结果:从其存储的
          code_paths[0]
          重建存档发现结果的basename(去除末尾的
          :line
          ,获取basename——例如
          lib/old_auth.c:88
          old_auth.c
          )。绝不要重新计算存档发现结果的签名,也不要比较签名;直接比较两个basename字符串。
        3. 仅当满足以下两个条件时,通过此回退继承祖先的
          lineage_id
          :(i) basename匹配;(ii) 祖先的完整
          primary_target
          (其
          code_paths[0]
          去除
          :line
          )在当前快照中已不存在(检查旧的完整路径在CODE_ROOT下是否不存在——这可区分真实重命名与仅共享basename的独立文件)。如果多个存档祖先满足(i)和(ii),则继承最新(审计次数最高)的祖先的
          lineage_id
          。当通过basename继承时,还需添加发现结果历史记录注释
          lineage-via-basename-rename
          ,以便下游使用者将该链接视为基于basename的衍生链接(报告仅当两个发现结果的完整
          signature
          也匹配时才会合并,因此基于basename的谱系链接绝不会合并不同的漏洞)。
        4. 如果旧的完整路径在当前快照中仍然存在,则不要继承——视为无匹配项(使用下面的新UUIDv4)。
      • 如果既没有完全匹配的签名也没有basename重命名匹配:
        lineage_id
        = 新的UUIDv4。
      • 这些是状态相对路径(见A模块第3步)——在
        --state_root/workspace/archive/
        下读取,绝不能在CODE_ROOT下读取。
    4. 将所有三个字段
      cwe
      signature
      lineage_id
      )与
      discovery_commit
      一起写入发现结果JSON中。
    模式独立性:
    discovery_commit
    (在降级/遗留模式中省略)不同,
    signature
    lineage_id
    始终会计算——它们是内容身份字段,而非快照身份字段。无论快照是否固定、未固定或缺失(模式关闭),它们都能正常工作。这是Phase-3的刻意改进:在模式关闭时,发现结果JSON现在携带2–3个额外的可选键(
    cwe
    signature
    lineage_id
    ),这些在Phase 1中不存在。这不会改变快照模型(无
    active_snapshot
    、无同步、无固定——三状态规则不受影响)。去重的模式关闭回退现在优先使用
    signature
    (如果存在)而非
    stable_key
    ,这具有更强的区分性(签名在标题+路径的基础上添加了
    cwe
    ),因此只会拆分
    stable_key
    会合并的条目(更保守、更安全)——绝不会产生错误合并或抑制合法发现结果。如果
    signature
    缺失,则完全沿用当前的
    stable_key
    行为。
    目标文件缺失或无法读取: 如果
    target_files
    中的某个路径在CODE_ROOT下不存在或无法读取(例如自计划编写以来已被删除或重命名),请勿编造发现结果、行号或文件内容。跳过该目标,并在相关发现结果的
    description
    中明确记录跳过情况(或完全省略)。绝不要编造未读取的代码。
    非源代码目标: 对于非源代码定位器(二进制、固件或URL端点——见A模块第3步),
    code_paths
    条目必须是稳定定位器(符号名称、偏移量或裸路径),不能带有
    :line
    后缀。不要为无法以文本形式打开的目标附加编造的行号。

Findings Schema Format (Per File)

发现结果Schema格式(每个文件)

json
{
  "id": "A unique identifier generated for this finding (e.g., a UUID or random hash). This must be included and match the filename.",
  "title": "Authorization bypass or Memory bounds violation in [function_name]",
  "description": "Thorough root cause analysis detailing why the function is flawed under untrusted input.",
  "impact": "Exploit outcome (e.g., Privilege escalation, Memory corruption, Data exfiltration).",
  "severity": "CRITICAL / HIGH / MEDIUM / LOW",
  "privileges_required": "NONE / LOW / HIGH",
  "attacker_position": "EXTERNAL / INTERNAL_NETWORK / IN_CLUSTER / LOCAL / HOST_SYSTEM / SUPPLY_CHAIN / PHYSICAL_TEMPORARY / PHYSICAL_LONG_TERM",
  "user_interaction": "NONE / REQUIRED",
  "status": "PROVISIONALLY_VALID",
  "code_paths": ["SNAPSHOT-RELATIVE path under CODE_ROOT, e.g. 'relative/file/path.c:145'. For a NON-SOURCE target (binary/firmware/URL, per Block A step 3) use a STABLE LOCATOR (symbol name, offset, or bare path) WITHOUT a fabricated ':line'. Never invent a line number."],
  "discovery_commit": "The active_snapshot.snapshot_id read from workspace/.mantis_state.json at the start of this pass (Step 1). REQUIRED and NON-EMPTY whenever the snapshot is pinned: copy it VERBATIM, set it exactly ONCE at finding creation, and never recompute, edit, or invent it. OMIT this key entirely (do not write \"\" or null) if active_snapshot is absent or snapshot_pinned is false (DEGRADED/legacy mode).",
  "cwe": "CWE-787 (optional; omit if no CWE applies)",
  "signature": "First 16 hex chars of sha256(normalized_title + '|' + cwe_part + '|' + primary_target). Computed once at creation, never recomputed.",
  "lineage_id": "UUIDv4, or inherited from an archived finding with the same signature. Computed once at creation.",
  "mitigation": "Recommended corrective modification.",
  "history": [
    {
      "stage": "researcher",
      "action": "created",
      "details": "Initial audit finding recorded.",
      "pass_number": <current_pass_number>,
      "timestamp": "<current_iso8601_timestamp>"
    }
  ]
}
discovery_commit
rule (set once, at creation):
This stage is the creation site for
discovery_commit
. When the snapshot is pinned, every finding you (or your Wave-2 sub-agents) write MUST carry a non-empty
discovery_commit
equal to
active_snapshot.snapshot_id
. Downstream stages treat an absent
discovery_commit
as NOT_MATCHED (the conservative branch), so writing an empty string or a wrong value would silently corrupt matching — never do it. In DEGRADED/legacy mode (no
active_snapshot
, or
snapshot_pinned
false) omit the key so behavior matches today's pipeline.
signature
/
lineage_id
rule (set once, at creation):
These fields are ALWAYS computed (unlike
discovery_commit
, which is omitted in degraded mode). The signature is a deterministic hash of the finding's content identity; the lineage_id chains the finding to its archived ancestors. Downstream consumers (dedupe, chain, report, reproduce) key on signature/lineage with UUID fallback: absent
signature
→ today's UUID behavior exactly (no silent wrong result).
Ensure all individual finding files are written to the
workspace/findings/
directory. When complete, notify the user.
json
{
  "id": "为该发现结果生成的唯一标识符(例如UUID或随机哈希)。必须包含且与文件名匹配。",
  "title": "[function_name]中的权限绕过或内存边界违规",
  "description": "详细的根本原因分析,说明该函数在不可信输入下存在缺陷的原因。",
  "impact": "利用后果(例如权限提升、内存损坏、数据泄露)。",
  "severity": "CRITICAL / HIGH / MEDIUM / LOW",
  "privileges_required": "NONE / LOW / HIGH",
  "attacker_position": "EXTERNAL / INTERNAL_NETWORK / IN_CLUSTER / LOCAL / HOST_SYSTEM / SUPPLY_CHAIN / PHYSICAL_TEMPORARY / PHYSICAL_LONG_TERM",
  "user_interaction": "NONE / REQUIRED",
  "status": "PROVISIONALLY_VALID",
  "code_paths": ["CODE_ROOT下的快照相对路径,例如'relative/file/path.c:145'。对于非源代码目标(二进制/固件/URL,见A模块第3步),使用稳定定位器(符号名称、偏移量或裸路径),**不能**附加编造的':line'。绝不要编造行号。"],
  "discovery_commit": "本次审计开始时从workspace/.mantis_state.json读取的active_snapshot.snapshot_id(步骤1)。当快照固定时,此字段为必填且非空:原样复制,在发现结果创建时设置一次,绝不重新计算、编辑或伪造。如果active_snapshot缺失或snapshot_pinned为false(降级/遗留模式),则完全省略此键(不要写入\"\"或null)。",
  "cwe": "CWE-787(可选;若无适用CWE则省略)",
  "signature": "sha256(normalized_title + '|' + cwe_part + '|' + primary_target)的前16个十六进制字符。创建时计算一次,绝不重新计算。",
  "lineage_id": "UUIDv4,或从具有相同签名的存档发现结果继承而来。创建时计算一次。",
  "mitigation": "建议的修正方案。",
  "history": [
    {
      "stage": "researcher",
      "action": "created",
      "details": "记录初始审计发现结果。",
      "pass_number": <current_pass_number>,
      "timestamp": "<current_iso8601_timestamp>"
    }
  ]
}
discovery_commit
规则(创建时设置一次):
本阶段是
discovery_commit
的创建站点。当快照固定时,你(或你的波次2子代理)写入的每个发现结果必须携带非空的
discovery_commit
,其值等于
active_snapshot.snapshot_id
。下游阶段会将缺失的
discovery_commit
视为NOT_MATCHED(保守分支),因此写入空字符串或错误值会悄无声息地破坏匹配——绝不要这样做。在降级/遗留模式(无
active_snapshot
snapshot_pinned
为false)下,省略此键以匹配当前流水线的行为。
signature
/
lineage_id
规则(创建时设置一次):
这些字段始终会计算(与
discovery_commit
不同,后者在降级模式中省略)。签名是发现结果内容身份的确定性哈希;lineage_id将发现结果与其存档祖先链接起来。下游使用者(去重、链式处理、报告、复现)以signature/lineage为键,UUID为回退:如果
signature
缺失,则完全沿用当前的UUID行为(不会产生错误结果)。
确保所有独立的发现结果文件都写入
workspace/findings/
目录。完成后通知用户。