Decepticon Verifier Agent:零误报漏洞验证门的原理、工具链与实战操作指南

原创2026-10-12 09:15:221,729 阅读

Decepticon Verifier Agent:零误报漏洞验证门的原理、工具链与实战操作指南

导读

本文围绕 Decepticon 中独立负责漏洞验证的 Verifier Agent 展开:它承接五阶段漏洞研究管线(scan → detect → verify → patch → exploit)中的 Stage 3,对检测器产出的候选漏洞(VULNERABILITY)在隔离沙箱中执行「最小正向 PoC + 强制负向对照」,只有 validated=true 的结果才被允许提升为正式 finding,从而充当整条管线的 零误报(Zero-False-Positive, ZFP)质量门。读完本文,你将掌握 validate_workspace_finding 工具的完整调用契约、六个参数的含义与合法取值、证据持久化规范(findings/evidence/ + evidence_pointer)、判别式证明的质量标准,以及该工具在源码层面的判定逻辑与 CVSS 3.1 评分实现。


一、Verifier 的定位:为什么误报比漏报更危险

在 Decepticon 的五阶段漏洞研究管线中,每个子 Agent 承担单一职责。Verifier 是 Stage 3,负责把检测器的「疑似漏洞」变成「可消费的证明」。这一职责由 verifier.md 的系统提示词明确刻画:

You are the Decepticon Verifier — the zero-false-positive gate. Given a candidate and its handoff evidence, build the smallest safe reproduction, prove or reject it in the sandbox, and persist the result under findings/evidence/.

其设计哲学在提示词中有一句点睛之笔:False negatives can be retried; false positives poison every downstream decision.(假阴性可以重试;假阳性会污染每一个下游决策。)因为 Patcher(Stage 4)只会对 validated=True 的漏洞打补丁,Exploiter(Stage 5)只会武器化已验证的原语——一旦验证环节放行了一个假阳性,后续所有阶段都会在错误的输入上浪费推理预算,甚至产生伪造的修复或利用链。因此 Verifier 提示词把「不静默丢弃失败」「被拒绝的尝试必须记录原因和下一步判别实验」写进了强制规则。

在 vulnresearch.md 编排器的视角中,Verifier 的角色被概括为:

verifier — Stage 3. Crafts PoCs, runs them under ZFP, promotes validated bugs to FINDING nodes with CVSS.

对应地,编排器只有在图中存在 vulnerability > 0 且 validated != True 的节点时才会启动 Verifier 批次,并以「PoC-First 顺序」要求它优先检索 GitHub/ExploitDB/NVD 上的现成 PoC、适配到目标后再自研新 harness。

从源码结构看,Verifier 作为一个独立 SubAgent 由 agents/plugins/verifier.py 的 create_verifier_agent() 工厂构建,其注册信息(SUBAGENT_SPEC)声明为「Stage 3 — triage and verification」,父 Agent 为 vulnresearch,工具面固定为 WORKSPACE_VERIFICATION_TOOLS + BASH_TOOLS,并挂载 SKILLS → FILESYSTEM → SANDBOX_NOTIFICATION → MODEL_FALLBACK → SUMMARIZATION → PROMPT_CACHING → PATCH_TOOL_CALLS 的中间件槽位。

二、当前工具面:workspace-first 的 validate_workspace_finding

2.1 背景:图后端暂缓,工作区文件成为真相来源

提示词开头的 NOTICE 段交代了一个重要的阶段性事实:图后端(kg_* 工具族)在 Neo4j 中间件重构期间暂时停用,Verifier 提示词中与图相关的完整流程处于 parked(搁置)状态。取而代之的是 workspace-first 的验证方案:

validate_workspace_finding is available now: it executes a positive PoC and mandatory negative control in the sandbox without requiring a graph backend. Workspace artifacts are the source of truth for this stage.

即:当前阶段不需要知识图谱参与即可完成验证,findings/、recon/ 等工作区文件就是阶段间的真相来源。这也解释了为什么 agents/plugins/verifier.py 的注释会强调「workspace verifier is graph-independent and enforces positive + negative controls, so the specialist can produce promotable evidence today」。

2.2 工具签名与参数契约

validate_workspace_finding 的实现位于 tools/verification.py,完整签名如下:

@tool
async def validate_workspace_finding(
    finding_id: str,
    poc_command: str,
    success_patterns: str,
    negative_command: str,
    negative_patterns: str,
    cvss_vector: str,
) -> str:

六个参数全部必填,任何一个为空或纯空白都会在执行前直接返回错误 JSON(mandatory verification fields are missing + 缺失字段列表),而不会触碰沙箱——这一点有单元测试专门锁定(见下文第八节)。各参数含义如下:

参数 类型 含义 要点
finding_id str 候选漏洞/发现标识 对应 FIND-NNN 或检测器产出的 vuln ID,会写入结果 JSON 的 vuln_id 字段
poc_command str 最小正向 PoC 命令 在沙箱内执行的 shell 命令,必须是能触发所声称影响的最小复现
success_patterns str 逗号分隔的 Python 正则列表 命中任一模式即视为成功信号;空列表直接报错
negative_command str 等价的负向对照(基线)命令 同一路由、同一认证上下文,但不含利用条件
negative_patterns str 基线期望出现的模式 基线必须命中这些模式才算「对照有效」
cvss_vector str 完整 CVSS 3.1 基础向量 必须形如 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H,缺一不可

调用成功后,工具返回一个 JSON 字符串(由 PoCResult.to_dict() 序列化,见 tools/research/poc.py),字段包括:

{
  "validated": true,
  "vuln_id": "FIND-007",
  "summary": "1 success signals, 1 negative hits, neg_ran=True, exit=0",
  "success_signals": ["proof-marker"],
  "negative_signals": ["ordinary response"],
  "stdout_excerpt": "...(截断 800 字符)",
  "stderr_excerpt": "...(截断 400 字符)",
  "exit_code": 0,
  "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
  "cvss_score": 10.0,
  "severity": "critical",
  "output_hash": "a1b2c3d4e5f6a1b2",
  "duration_seconds": 1.234
}

其中 output_hash 是对 stdout + "||" + stderr + "||" + exit_code 计算的前 16 位 SHA-1,作为证据指纹;stdout_excerpt/stderr_excerpt 是原始输出的带界截断,呼应提示词「Keep raw response output in evidence files」的要求——原始输出必须完整留在证据文件里,JSON 里只放摘要信号。

2.3 沙箱执行机制

命令在 Docker 隔离沙箱中执行,由 tools/bash/bash.py 的 get_sandbox() 提供。sandbox_runner() 将 HTTPSandbox 适配为 (stdout, stderr, exit_code) 的 runner:

  • 优先调用 execute_tmux_async,退化路径走 execute_tmux 线程包装;tmux 会话名固定为 poc,内层命令超时 60 秒;
  • 外层有 120 秒的总预算(POC_RUNNER_TIMEOUT_SECONDS),防止 tmux 挂死导致验证无限期阻塞;
  • 超时返回 [POC_TIMEOUT] 前缀的 stderr,其他沙箱故障返回 [SANDBOX_ERROR] 前缀,调用方据此区分「命令跑挂」与「沙箱故障」;
  • 从输出中正则解析 [Exit code: N] 恢复退出码。

三、提升的强制门槛(CRITICAL_RULES)

Verifier 提示词的 CRITICAL_RULES 定义了「什么才能算数」的硬约束,共五条:

  1. 每次提升(promotion)必须调用 validate_workspace_finding,且必须同时提供:正向命令、成功模式、等价的负向对照命令、负向对照模式、完整的 CVSS 3.1 向量。任何手工「我觉得没问题」的推断都不能替代这次调用。
  2. 证据先行:在写 findings/FIND-NNN.md 之前,必须先把工具返回的 JSON 持久化到 findings/evidence/FIND-NNN_verification.json,并在 finding 中把该路径作为 evidence_pointer 引用。也就是说,文档依赖证据文件,而不是反过来。
  3. 只提升 validated=true。特别强调:「A baseline that matches a success pattern is noise, not confirmation.」(基线匹配了成功模式 = 噪音,不是确认。)这正是 ZFP 判定的核心语义,见第六节源码解读。
  4. 失败不许静默丢弃:被拒绝的尝试必须在 finding 或 handoff 笔记中记录确切原因 + 下一个判别实验(discriminating experiment),让后续 Agent 能区分「假假设」与「环境故障」。
  5. 边界纪律:绝不编辑源文件(补丁属于 Patcher 阶段);绝不跑宽泛扫描(那是 Scanner 的职责);只复现 handoff 给定的候选。

这些约束在 agents/plugins/verifier.py 的工具面(WORKSPACE_VERIFICATION_TOOLS + BASH_TOOLS)中得到了结构性呼应:Bash 工具只用于「启动服务、暂存 PoC、跑 curl 冒烟」,验证本身必须走 validate_workspace_finding。

四、操作循环(OPERATING_LOOP):六步走完一次验证

提示词给出了一套 6 步操作循环,是 Verifier 每次被派发时的固定流程:

  1. 读取输入:读候选 handoff、目标范围(scope)和已存在的证据文件。在 Decepticon 的编排约定下,这通常还包括 docs/agent-file-mapping.md 所描述的 recon/SUMMARY.md 与既有 findings/FIND-*.md,用于还原漏洞机制与前置条件。
  2. 按需启动目标:只有当 handoff 明确要求时才启动本地或授权目标,并记录就绪检查(readiness check)。例如用 Bash 工具拉起靶机服务、执行 curl 冒烟确认可达。
  3. 设计实验对:设计一个最小正向命令 + 一个等价良性基线。关键要求是:成功模式必须能识别所声称的影响本身(impact-specific marker),「仅状态码差异不算证明」。
  4. 运行工具:调用 validate_workspace_finding,传入六参数。CVSS 向量必须完整,且只有被观测结果实际证明的指标才允许填——提示词明确举例:只有攻击面确实满足时,才可使用 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H 这样的全高配向量,禁止为了分数好看而虚报指标。
  5. 保存 JSON 并产出文档:先保存 findings/evidence/FIND-NNN_verification.json;验证成功则按 finding-protocol 技能写操作型 finding;失败则写「尝试过的证明 + 拒绝原因」,供下一 Agent 区分假假设与环境故障。
  6. 返回精简账本:以 verified N/M; rejected M; evidence: <paths> 的形式收尾,让编排器只凭一句话就能判断批次产出。

这套循环与 finding_verifier.md(独立裁决员提示词)互补:前者负责在沙箱里制造证明,后者负责对既有的 FIND-NNN.md 做独立复核并给出 verification_status(verified / false_positive / unverified)与 verification_rationale。两者都强调「先前 Agent 的信心不是证据」。

五、证明质量:判别信号、对照纪律与竞态处理(PROOF_QUALITY)

PROOF_QUALITY 段是提示词中技术含量最高的部分,四条质量标准逐条展开:

  • 用影响特定标记(impact-specific marker):不要用「HTTP 200」或「没有报错」这类弱信号。例如反射型 XSS 应该匹配响应中回显的 payload 本体,SQL 注入应该匹配数据库错误指纹或时间差,RCE 应该匹配 id 命令的实际输出。状态码差异同样不够——源码中 tools/research/poc.py 的判定只认模式命中,从不把 exit code 当成功信号。
  • 负向对照必须是「同一路由 + 同一认证上下文、仅去掉利用条件」:比如 SQL 注入的基线是同一 URL 的合法参数请求,路径穿越的基线是同一上传接口的普通文件名。只有这种「差一个条件」的对照才能把「因为 payload 才生效」和「本来就这样」区分开。
  • 原始输出进证据文件,finding 正文只写判别信号:stdout/stderr 的完整原始响应保存到 findings/evidence/FIND-NNN_*.txt,finding 正文只保留成功/基线命中的模式与一行摘要。
  • 竞态敏感声明必须做多次试验:单个非确定性结果不足以证明竞态类漏洞(如 TOCTOU、use-after-free 窗口)。要求记录全新状态(fresh-state)下的多次试验与观察到的成功率(如 success 3/5),而不是只提交一次侥幸命中的输出。

这三个方向(信号强度、对照等价性、证据保真)最终都汇入同一条 ZFP 原则:验证不是「有反应」,而是「正向有、基线无、且两者都被证明」。

六、源码级原理:validate_poc 的 ZFP 判定与 CVSS 评分

6.1 三段式判定逻辑

validate_workspace_finding 的核心委托给 tools/research/poc.py 的 validate_poc()。判定按以下顺序进行:

  1. 正向信号:对 stdout + "\n" + stderr 合并后的 blob,用 re.search(pat, blob, re.DOTALL | re.IGNORECASE) 逐个匹配 success_patterns;若某条模式本身不是合法正则(re.error),则降级为大小写不敏感的子串匹配。
  2. 负向对照:执行 negative_command,同样匹配 negative_patterns 得到 neg_hits。ZFP 核心:若基线输出也命中了任何成功模式,说明这个信号「本来就出现」,属于噪音,此时直接把成功信号清空(success = [])并记录 demoting 警告。
  3. 综合判定:
validated = bool(success) and (not neg_ran or bool(neg_hits))

即:正向命中 且(未跑基线 或 基线命中其预期模式)。注意 neg_ran 的语义——提示词要求负向对照为必填,工具层对缺失字段会直接拒绝执行,因此 neg_ran=False 的分支在实际调用中几乎不可达,属于防御性兜底。

关键推论:基线「命中成功模式」会导致整体降级(第 2 步),而基线「未命中基线模式」会导致 neg_hits 为空进而 validated=False(第 3 步)。两种失败路径都被记录,且 summary 会呈现 N success signals, M negative hits, neg_ran=True, exit=0 的完整拼图,便于后续判别。

6.2 CVSS 3.1:不臆造指标、不依赖外部计算器

parse_cvss_vector() 对向量做严格校验:

  • 必须以 CVSS:3.1/ 开头;
  • 必须恰好包含 AV/AC/PR/UI/S/C/I/A 八个指标,重复、未知指标、缺指标都会直接抛 ValueError 并被工具层捕获返回 bad CVSS vector 错误;
  • 各指标的合法取值由枚举限定:AV ∈ {N,A,L,P}、AC ∈ {L,H}、PR ∈ {N,L,H}、UI ∈ {N,R}、S ∈ {U,C}、C/I/A ∈ {N,L,H}。

CVSSVector.base_score() 按标准 v3.1 公式计算(Scope 未变时 impact = 6.42 × ISS,变更时用 7.52 × (ISS−0.029) − 3.25 × (ISS−0.02)¹⁵,exploitability = 8.22 × AV × AC × PR × UI,结果向上取整到 0.1),to_severity() 映射为 CRITICAL(≥9.0)/ HIGH(≥7.0)/ MEDIUM(≥4.0)/ LOW(>0)/ INFO(0)。这意味着 Verifier 可以在不联网的情况下为每个已验证 finding 附上一个可辩护的数值分数与完整向量,而 finding-protocol 技能(skills/shared/finding-protocol/SKILL.md)也明确要求「记录 CVSS 时必须同时存向量、版本与数值,且数值必须来自确定性工具而非由严重性标签反推」。

6.3 图后端(可选路径)与遥测

validate_poc 还接受可选的 graph: KnowledgeGraph 参数:若提供图后端,验证成功会把 vuln 节点标记 validated=True 并创建 FINDING 节点,以 VALIDATES、MAPS_TO 边回指;验证失败也会记录(validated=False),保留尝试历史。同时,_persist_result() 会在 FINDING 节点诞生的唯一位置发出真实分类的埋点遥测(severity/CWE/MITRE 取自 vuln 节点,绝不包含目标、标签或 stdout),且任何遥测异常都被吞掉以保证工具永不因遥测失败而中断——这从工程上落实了「telemetry must never break the tool」。当前 workspace-first 路径下该参数为 None,属于未来 Neo4j 中间件重构落地后的能力预留。

七、证据落盘与下游衔接:一份 finding 的完整生命周期

7.1 命名约定与 evidence_pointer

验证结果按以下约定落盘:

  • 原始响应:findings/evidence/FIND-NNN_<slug>.txt(如 FIND-001_rce_confirm.txt);
  • 验证 JSON:findings/evidence/FIND-NNN_verification.json(由 validate_workspace_finding 的返回值直接写入);
  • 操作型 finding:findings/FIND-NNN.md,frontmatter 必须把验证 JSON 路径写进 evidence_pointer。

skills/shared/finding-protocol/SKILL.md 给出最小模板,其中 ## Verification 章节正是为 Verifier 预留的:status(confirmed/rejected)、正向命令与判别信号、等价负向对照与基线信号、verification.json 路径、确认时的 CVSS 向量。此外还有可选的 evidence_manifest 结构化引用(path/label/role/supports)与 location 定位符(http、net、code、pkg、cloud、identity、mobile、device 八种 scheme)。

7.2 证据契约校验

对本地 engagement 工作区,可运行证据契约校验器验证引用完整性(docs/tools/local-evidence-contract.md):

DECEPTICON_SKIP_BOOT=1 python -m decepticon.tools.reporting.evidence_contract \
  --root ~/.decepticon/workspace/example findings/FIND-001.md

输出 JSON 包含 valid、references、errors,退出码 0 表示契约通过。该契约只验证「声明的引用格式正确且文件可访问」,不证明 finding 本身——valid 不等于 verified,两个概念在 Decepticon 中严格分离。

7.3 下游:Patcher 复用同一组判别参数

验证结果不只是写进文档,还成为下一阶段的输入契约。在 patcher.md 中,Patcher 被要求「只修补 validated=True 的漏洞」,并在打补丁后调用 patch_verify(patch_id=..., poc_command=<the verifier's PoC>, success_patterns=<same list>, ...)——PoC 命令和成功模式必须与 Verifier 使用的一致。也就是说,Verifier 产出的实验对(正向命令 + 成功模式)是「修复是否成立」的回归基准:补丁正确 = 原来的正向信号消失、基线信号保留。若找不到这些参数,Patcher 应回读 finding 的 stdout_excerpt 等证据字段。

同样地,分析师侧的 patch-diff/SKILL.md 要求每个候选变体「各自拥有独立的正向 + 基线命令,通过 validate_workspace_finding 验证,不得继承原 CVE 的验证结论」;pattern-exhaustion/SKILL.md 要求对同一根因模式的每个实例单独验证,只有 validated=true 才独立成 finding;模糊测试技能 fuzzing/SKILL.md 则以崩溃签名作为正向信号、良性语料执行作为负向对照——这些是 validate_workspace_finding 在验证器之外的典型复用场景。

八、测试与可验证性:ZFP 约束有单元测试背书

Verifier 的 ZFP 约束并非只停留在提示词层面,tests/unit/tools/test_verification.py 用两个用例锁定了核心行为:

  1. test_workspace_verifier_requires_and_records_negative_control:构造一个只认识 positive/negative 两条命令的假沙箱,调用工具后断言 validated is True、success_signals == ["proof-marker"]、negative_signals == ["ordinary response"]——证明「正向命中 + 基线命中其预期模式」会得到通过,且两类信号都被如实记录。
  2. test_workspace_verifier_rejects_missing_control_before_execution:传入空 negative_command,并把 get_sandbox 替换为「一旦被调用就抛断言」的桩,断言返回 mandatory verification fields are missing——证明缺失负向对照的命令根本不会触碰沙箱,从调用入口就杜绝了「没跑基线却声称验证通过」的可能。

配合 tools/verification.py 中「先校验六字段、再校验 CVSS 向量、再校验模式非空、最后才执行」的防御顺序,Verifier 的零误报承诺在工程层面得到了三层保障:提示词纪律 → 工具参数强校验 → 单元测试回归。

九、使用小结与注意事项

把 Verifier 跑好,本质上是在执行一个「最小实验设计」协议:

  • 实验对:一个能触发所声称影响的最小命令 + 一个同路由同认证、仅缺利用条件的基线;
  • 信号:影响特定、可判别的模式,而非 200 或非零退出码;
  • 证据:原始输出落 findings/evidence/,工具 JSON 落 FIND-NNN_verification.json 并被 evidence_pointer 引用;
  • CVSS:完整八指标向量,且每个指标都必须有观测结果支撑;
  • 失败:写明原因与下一个判别实验,绝不静默丢弃;
  • 边界:只复现给定候选,不扫描、不修源码。

需要留意的前提与限制:当前 kg_* 图工具因 Neo4j 中间件重构而停用,验证完全依赖工作区文件,阶段间状态靠 findings/、recon/ 与 handoff 摘要传递;validate_workspace_finding 依赖已初始化的 HTTPSandbox(set_sandbox 在 Agent 构建期注入);竞态类漏洞需要多次 fresh-state 试验而非单次命中。理解这些边界后,无论是作为 vulnresearch 管线的 Stage 3 使用,还是在 patch-diff、pattern-exhaustion、fuzzing 等分析师工作流中复用,都能让每个被提升的 finding 拥有可复现、可辩护、可审计的证明链。

延伸阅读:verifier.md 提示词(本文依据)|验证工具实现|PoC 校验与 CVSS 核心逻辑|Verifier Agent 工厂|验证单元测试|finding-protocol 技能|独立裁决员提示词|证据契约文档

登录后查看全文
Decepticon