关于 mantis-researcher
mantis-researcher 是安全审计技能,会按计划检查源码中的内存安全、逻辑正确性和健壮性问题。环境支持时,它可用并行子 Agent 分阶段筛查和深入审计,并将结构索引作为全面搜索的补充。
主要功能
- 01按计划审计源码
- 02检查内存安全与逻辑问题
- 03并行开展初筛和深入审计
- 04将结构索引作为搜索线索
- 05逐个写入 JSON 问题文件
适用场景
- 01检查目标文件中的越界风险或缺失检查
- 02追踪 API 调用点并核对大小约束
- 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对重复发现进行归类和合并。
- 将新发现写入具有唯一 UUID 的独立文件。后续步骤依靠
说明
步骤 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)。
对目标代码库进行全面的内存安全、逻辑正确性和健壮性审查。
按以下步骤执行研究阶段:
-
加载审查计划和上下文: 从
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。 -
子 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 调用点扫描。-
优先解析协议(任何结构查询前都必须执行):
- 首先解析符号:
python3 workspace/helpers/query_structural_index.py resolve_symbol --name "<function_name>" [--language "<lang>"] [--file "<path>"] --state_root <state_root> - 如果响应包含
ambiguous: true,请调查所有匹配的符号, 绝不能悄悄选取其中一个。如有可能,用--file/--language缩小范围, 或为每个匹配的符号安排调查。 - 使用解析得到的
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 的发现方式 (当前行为)。结构索引仅是覆盖率提示。
-
-
-
穷尽式接口与调用点审查: 如果目标源文件定义了公共函数或 API 函数 (例如数字解析器、解码器、编码器或转换器),并且文档明确说明了大小限制或 安全要求(例如要求调用方分配特定大小的缓冲区):
- 在整个仓库中对函数名运行 grep,构建完整的候选调用点集合——这是强制
最低要求。然后使用结构索引查询辅助工具(先执行
resolve_symbol,再执行find_callers)为调用点排序,优先审计排名靠前的调用点(索引能区分实际 调用与注释、字符串和变量名)。审查两组结果的并集——结构索引可能会遗漏 基于宏的调用、函数指针和动态分派,因此 grep 仍是最低要求。 - 搜索代码库,找出并审查整个仓库中这些函数的所有调用点,以确保全局遵守 安全约定。
- 阅读调用方文件,验证每个调用点是否严格遵守输入限制、正确管理边界并检查 大小。
- 将任何差异标记为约定不一致问题或缺少检查。
- 在整个仓库中对函数名运行 grep,构建完整的候选调用点集合——这是强制
最低要求。然后使用结构索引查询辅助工具(先执行
-
不受限制/探索性调查: 如果
workspace/plan.json中的调查计划包含明确要求进行不受限制的全面检查、对抗性审计或随机探索的指示或问题:- 忽略
workspace/kb/THREAT_MODEL.md中现有的安全假设和已记录的信任边界。 - 将所有输入和边界视为不可信且可能格式错误。
- 从头开始分析实现,并充分发挥自主性。
- 如果是指示极少的随机探索/挖掘任务,应着重梳理目标文件的行为、找出关键入口,并寻找意外副作用或边界情况,不受特定威胁模型的约束。
- 忽略
-
汇总并写入调查结果: 不要创建单个庞大的文件,而应在
workspace/findings/目录不存在时创建该目录。为每项潜在发现生成唯一 UUID,并将有效的 JSON 对象写入单独的文件workspace/findings/<id>.json。这样可以将各项发现隔离开来,避免后续分析时超出令牌限制。文件中的 JSON 前后不得包含任何文本。签名与沿袭计算(步骤 5a):
对每项发现,在写入 JSON 文件之前计算
signature、lineage_id,并视情况计算cwe:-
cwe(可选): 如果漏洞对应已知的 CWE,则将cwe字段设为其标识符(例如"CWE-787"、"CWE-416")。如果不适用任何 CWE,则省略该字段。这是签名的输入。 -
计算
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规则相同)。
-
计算
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)。而是:
- 计算当前发现项的 basename:取
primary_target(code_paths中第一个去掉末尾:line的条目,它已在步骤 2 中为 signature 计算完毕),并取其 basename(例如,src/auth.c→auth.c)。如果primary_target为空(非源码 LOCATOR 或code_paths为空),则跳过此回退——转到下方的全新 UUIDv4。 - 对步骤 3 的归档扫描中找到的每个已归档发现项:
根据其已存储的
code_paths[0]重建已归档发现项的 basename(去掉末尾的:line, 并取 basename——例如,lib/old_auth.c:88→old_auth.c)。不要重新计算已归档 发现项的 signature,也不要比较 signature;应将两个 basename 作为字符串进行比较。 - 仅当 (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)。 - 如果旧的完整路径在当前快照中仍然存在,则不要继承——视为未匹配(使用下方的全新 UUIDv4)。
- 计算当前发现项的 basename:取
- 如果精确 signature 和基于 basename 的重命名均未匹配:
lineage_id= 全新的 UUIDv4。 - 这些是相对于 STATE 的路径(Block A 步骤 3)——应在
--state_root/workspace/archive/下读取,绝不要在 CODE_ROOT 下读取。
- 扫描
-
写入全部三个字段(
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/ 目录。完成后通知用户。