mcpField
M
mantis-researcher

mantis-researcher

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

关于 mantis-researcher

mantis-researcher 是安全审计技能,会按计划检查源码中的内存安全、逻辑正确性和健壮性问题。环境支持时,它可用并行子 Agent 分阶段筛查和深入审计,并将结构索引作为全面搜索的补充。

主要功能

  1. 01按计划审计源码
  2. 02检查内存安全与逻辑问题
  3. 03并行开展初筛和深入审计
  4. 04将结构索引作为搜索线索
  5. 05逐个写入 JSON 问题文件

适用场景

  1. 01检查目标文件中的越界风险或缺失检查
  2. 02追踪 API 调用点并核对大小约束
  3. 03初筛后调查优先级较高的代码热点

安装

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

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

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

详细介绍

Mantis 研究员(/mantis-researcher)

系统目标

韧性代码审计员。快速分诊并深入审查源文件,以识别边界检查、前置条件、缺失的净化处理以及接口违规。

命令定义

  • 命令: /mantis-researcher
  • 描述: 根据 workspace/plan.json 中的策略审计生产源代码文件。
  • 参数(可选;由编排器提供,由 Block A 使用):
    • --snapshot_root / SNAPSHOT_ROOT:本轮固定的只读代码快照的绝对路径。该路径即 CODE_ROOT,所有相对于快照的路径字段都以此为基准解析(Block A 步骤 1b)。
    • --snapshot_id / SNAPSHOT_ID:本轮快照标识符。用于哨兵检查(Block A 步骤 2),并原样写入每条发现的 discovery_commit。
    • --state_root:workspace/ 状态目录的绝对路径(包含 plan.json、.mantis_state.json、findings/、kb/)。状态路径均相对于 STATE,绝不会以 CODE_ROOT 为前缀(Block A 步骤 3)。
    • 如果提供,也会采用 --target_root(权威覆盖项,Block A 步骤 1a)。
    • 所有标志均缺失 -> 降级/传统模式: CODE_ROOT 回退为当前目录,snapshot_pinned 视为 false,行为与当前完全一致(不写入 discovery_commit)。

输入/输出约定

  • 读取:
    • 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 对重复发现进行归类和合并。

说明

步骤 0:路径定位解析(支持快照的路径处理)

在以下编号研究步骤之前运行此项。它确定唯一的 CODE_ROOT,供本阶段中的所有 target_files / code_paths 引用解析,确保所有子 Agent 审计同一个固定快照。

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.

研究员专用说明:

  • 研究员属于代码读取阶段,因此 Block A 步骤 0 中仅处理发现的跳过规则不适用于此处——你必须解析 CODE_ROOT 并遵循哨兵检查。
  • workspace/plan.json 中的 target_files 和发现中的 code_paths 均相对于快照:在 CODE_ROOT 下解析(Block A 步骤 3)。
  • kb_references、workspace/plan.json、workspace/.mantis_state.json 以及 workspace/findings/ 下的所有内容均相对于 STATE:在 --state_root 下读取/写入,绝不能放在 CODE_ROOT 下。
  • 固定快照时,绝不要在 CODE_ROOT 下写入、编译或生成任何内容(Block 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 的值(参见 Findings Schema Format)。如果缺少 active_snapshot 或 snapshot_pinned 为 false,则 表示处于 DEGRADED/legacy 模式——不要停止(Block A 第 1d 步);只需省略 discovery_commit。

  2. 子 Agent 委派(基于波次的群体并行化): 如果 CLI 或 Agent 平台支持生成子 Agent(例如,使用专用子 Agent 工具或多 Agent 编排指令):

    • 如果支持子 Agent,不要按顺序执行调查。将 workspace/plan.json 中的调查项拆分为并行波次,以 最大限度提高吞吐量和上下文效率。

    • 第 1 波:轻量快速分诊(并发峰值): 生成并发的 轻量级子 Agent(例如,同时最多 10–20 个),扫描 workspace/plan.json 中列出的所有文件。每个子 Agent 只应输出快速分类:{"potentially_flawed": true/false, "reason": "..."}。

    • 第 2 波:深入审计安全缺陷热点并并行搜索攻击路径: 汇总第 1 波标记的所有文件。生成一波并发的深度审计子 Agent(例如,最多 4–8 个并行), 仅针对已识别的热点进行审查。对于特别复杂的文件,可生成多个子 Agent 审查同一个文件,采用不同的提示约束,或使用多种成本较低的 LLM,以并行探索不同攻击向量。依靠后续的去重阶段合并重叠的发现。

    • 令牌优化(分布式写入): 指示第 2 波子 Agent 生成唯一 UUID,并将其发现直接写入磁盘上的独立 workspace/findings/<id>.json 文件。不要要求它们在消息中向你返回完整的 JSON 载荷,因为汇总这些内容会耗尽你的上下文窗口。只要求它们返回所创建 UUID 的列表。

    • 快照隔离(固定波次快照)——强制要求: 将相同的 --snapshot_root(在第 0 步解析的 CODE_ROOT)和相同的 --snapshot_id 值传递给每个第 1 波和第 2 波子 Agent,并指示它们遵守 Block A(在该 CODE_ROOT 下解析 target_files/code_paths,并遵循哨兵规则)。任何写入发现的第 2 波子 Agent 都必须按照 Findings Schema Format 中的精确规定,将该 snapshot_id 写入 discovery_commit。子 Agent 不得运行 git pull/fetch/ checkout/reset、hg pull/update、repo sync,也不得运行任何会更改工作树或切换修订版本的命令——整个审查轮次期间快照均不可变。无法查看该快照的子 Agent 必须报告这一情况,不得重新同步。

    • 如果当前环境不支持子 Agent 或并发处理,则退回到按顺序执行扫描和深入审查。

    • 结构索引(仅作提示的增强): 如果结构索引可用(存在 workspace/kb/structural_index/manifest.json),请用它补充上述基于波次的 swarm。结构索引只决定顺序,绝不决定成员范围。绝不能用它替代穷尽式的 Step-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;绝不能用它替代穷尽式的 Step-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。这样可以将各项发现隔离开来,避免后续分析时超出令牌限制。文件中的 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 = 发现中存在且非 null 的 cwe 字段;否则为空字符串。
      • primary_target = code_paths 中第一个条目去除末尾的 :line 后的内容(例如,src/auth.c:145 → src/auth.c)。如果 code_paths 为空,或第一个条目是非源代码定位符(包含 :// 的 URL,或 Block A 步骤 3 所述的非文件符号/偏移量),则使用空字符串。以确定的顺序排列 code_paths(主要汇点优先,并在各轮之间保持稳定),以免 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 链,使使用方能够跨轮次折叠)。如果多个已归档发现项具有相同 signature,则继承自最新(轮次编号最高)的祖先。所有具有相同 signature 的祖先 SHOULD 共享同一个 lineage_id;如果不一致,则继承自最新的祖先并记录警告。
      • 如果精确 signature 未匹配:仅针对真正的重命名,尝试基于 basename 的重命名回退。 不要重新计算 signature(它在创建时仅计算一次,此后永不重新计算——不变量 #4)。而是:
        1. 计算当前发现项的 basename:取 primary_target(code_paths 中第一个去掉末尾 :line 的条目,它已在步骤 2 中为 signature 计算完毕),并取其 basename(例如, src/auth.c → auth.c)。如果 primary_target 为空(非源码 LOCATOR 或 code_paths 为空),则跳过此回退——转到下方的全新 UUIDv4。
        2. 对步骤 3 的归档扫描中找到的每个已归档发现项: 根据其已存储的 code_paths[0] 重建已归档发现项的 basename(去掉末尾的 :line, 并取 basename——例如,lib/old_auth.c:88 → old_auth.c)。不要重新计算已归档 发现项的 signature,也不要比较 signature;应将两个 basename 作为字符串进行比较。
        3. 仅当 (i) basename 匹配,且 (ii) 祖先的完整 primary_target(其 code_paths[0] 去掉 :line 后的路径)在当前快照中已不存在时,才通过此回退继承祖先的 lineage_id(检查 CODE_ROOT 下是否不存在旧的完整路径——这可以区分真正的重命名 与仅仅共享 basename 的另一个独立文件)。如果有多个已归档祖先满足 (i) 和 (ii), 则继承自最新(轮次编号最高)的祖先。通过 basename 继承时,还要添加一条发现项 历史备注 lineage-via-basename-rename,以便下游使用方将此链接视为基于 basename 得出(报告仅会在完整 signature 也相同、符合 SAME-BUG 谓词时折叠两个发现项, 因此,基于 basename 的 lineage 链接绝不会合并不同的 bug)。
        4. 如果旧的完整路径在当前快照中仍然存在,则不要继承——视为未匹配(使用下方的全新 UUIDv4)。
      • 如果精确 signature 和基于 basename 的重命名均未匹配:lineage_id = 全新的 UUIDv4。
      • 这些是相对于 STATE 的路径(Block A 步骤 3)——应在 --state_root/workspace/archive/ 下读取,绝不要在 CODE_ROOT 下读取。
    4. 写入全部三个字段(cwe、signature、lineage_id)到发现项 JSON 中, 与 discovery_commit 并列。 模式独立性: 与 discovery_commit(在 DEGRADED/legacy 模式下会省略)不同,signature 和 lineage_id 始终会计算——它们是内容身份字段,而非快照身份字段。无论快照已固定、未固定还是不存在(MODE-OFF),它们都能正常工作。这是 Phase-3 的一项有意改进:在 MODE-OFF 下,finding JSON 现在会带有 2–3 个 Phase 1 中不存在的额外可选键(cwe、signature、lineage_id)。这不会改变快照模型(没有 active_snapshot、没有同步、没有固定——3 状态规则不受影响)。现在,MODE-OFF 下的去重回退逻辑会在 signature 存在时优先使用它,而非 stable_key;这种方式的区分度严格更高(signature 在标题和路径组合中加入了 cwe),因此它只可能拆分原本会被 stable_key 合并的条目(预算重新分配,更保守 = 安全)——绝不会错误合并或抑制合法的 finding。若 signature 不存在 → 行为与当前的 stable_key 完全一致。

    目标文件缺失或无法读取: 如果 target_files 中的路径在 CODE_ROOT 下不存在或无法读取(例如,自计划编写后文件已被删除或重命名),请勿伪造 finding、行号或文件内容。跳过该目标,并在其相关 finding 的 description 中如实记录跳过原因(也可以完全省略)。绝不可编造未读取过的代码。

    非源代码目标: 对于非源代码 LOCATOR(二进制文件、固件或 URL 端点——参见 Block A 第 3 步),code_paths 条目必须是稳定的 LOCATOR(符号名称、偏移量或不带其他内容的路径),且不得带 :line 后缀。不要为无法作为文本打开的目标附加伪造的行号。

Findings Schema Format (Per File)
{
  "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 规则(创建时设置一次): 此阶段是创建 discovery_commit 的位置。快照已固定时,你(或你的 Wave-2 子 Agent)编写的每个 finding 都必须带有非空的 discovery_commit,其值等于 active_snapshot.snapshot_id。下游阶段会将缺少 discovery_commit 的情况视为 NOT_MATCHED(保守分支),因此写入空字符串或错误值会悄无声息地破坏匹配——绝不可这样做。在 DEGRADED/legacy 模式下(没有 active_snapshot,或 snapshot_pinned 为 false),请省略此键,使行为与当前流水线一致。

signature/lineage_id 规则(创建时设置一次): 这些字段始终会计算(不同于在降级模式下会省略的 discovery_commit)。signature 是 finding 内容身份的确定性哈希;lineage_id 将 finding 与其归档的祖先关联起来。下游使用方(去重、链、报告、复现)会以 signature/lineage 为键,并在缺失时回退到 UUID:若 signature 不存在 → 行为与当前的 UUID 完全一致(不会悄无声息地产生错误结果)。

确保将所有单独的 finding 文件写入 workspace/findings/ 目录。完成后通知用户。

评论

支持 Markdown