defending-code-reference-harness 实战指南:用 /triage 技能将数百条扫描发现收敛为可执行的安全工单

原创2026-09-27 23:02:231,452 阅读
文章标签:应用安全漏洞扫描AI Agent人工智能

defending-code-reference-harness 实战指南:用 /triage 技能将数百条扫描发现收敛为可执行的安全工单

安全扫描的产出从来不是终点,而是新的起点。当 /vuln-scan、vuln-pipeline 或其他任何扫描器一次吐出成百上千条原始发现时,工程师真正需要的是一个短小、排序、有人负责的清单,而不是又一座待读的"报告山"。本指南以仓库中的 triage 文档 为核心骨架,完整讲解 /triage 技能的四大处理环节(验证、去重、重排、路由)、三条可配置规则、全部命令行用法,并结合 harness/dedup.py、docs/customizing.md、docs/troubleshooting.md 等仓库源码与配套文档,把"如何处理海量扫描结果"变成一套可复制、可验证、可交给下游工单系统的工程流程。

/triage 是什么:把"发现堆"变成"行动清单"

/triage 是 defending-code-reference-harness 仓库中一个交互式安全技能(Skill),与 /threat-model、/vuln-scan、/patch、/customize 并列,定位在扫描之后、修复之前。它的输入是任何形式的原始发现——无论来自当前仓库的 vuln-pipeline、交互式 /vuln-scan 输出,还是历史遗留的旧扫描器结果;它的输出是两份工件:

  • TRIAGE.md:人类可读的、按优先级排序的发现清单;
  • TRIAGE.json:机器可读的发现清单,可直接喂给工单系统(tracker)或下游消费程序。

正如仓库 CLAUDE.md 所描述的技能分工,/triage 的职责是"verify + dedupe + rank a findings pile"。它在**单次遍历(single pass)**内完成四件事:

  1. 验证(Verify):以对抗性(adversarial)方式逐条把每个发现与源码对照——注意是只读检查,不执行代码——然后丢弃那些站不住脚的发现。
  2. 去重(Deduplicate):把同一根因(root cause)在并行运行或多扫描器场景下被重复报告的 N 条发现折叠为一条。
  3. 重排(Re-rank):基于前置条件(preconditions)和你声明的信任边界(trust boundary)推导真实严重度。例如,一个标注 "HIGH" 的漏洞,如果背后还有一两条前置条件且需要认证访问,就会被降级为 MEDIUM。
  4. 路由(Route):为每个幸存下来的发现打上组件负责人(component owner)标签,以便分派到合适的处理人。

这套"先验证、再去重、再重排、最后路由"的顺序本身就是防疲劳设计的核心:如果不先砍掉假阳性和重复项就急着排序,工程团队很快会被噪音淹没。

它应用的两条规则:重复判定与严重度映射

重复判定规则(Duplicates)

两条发现是否算重复,判据只有一条:修好其中一条,另一条是否也随之消失(fixing one fixes the other)。以此为目标,技能用两轮扫描来识别:

  • 第一轮:廉价确定性扫描(cheap deterministic pass)。检查两条发现是否满足:位于同一文件、类别相同、且引用的行号相差在十行以内(within ten lines)。
  • 第二轮:LLM 语义扫描(LLM pass)。让模型用语义推理识别第一轮漏掉的重复项——例如同一根因的不同表述、同一漏洞在多个调用点的报告、同一缺失的全局防护(如鉴权检查)被按端点逐个上报、同一路径上同时被标记的"原因"与"后果"。

反过来,以下情况应判为不同发现:同一文件中的不同漏洞类别;到达不同 sink 的不同变量;同一辅助函数里的两个独立 bug;以及两个端点各缺同一个检查、但各自需要独立修复的情况。

值得注意的是,重复判定的"因果视角"并不局限于交互式技能。在仓库的流水线侧,harness/dedup.py 提供了一种更轻量的汇总式去重:遍历 results 目录下所有 result.json,以 (crash_type, top ASAN frame) 为签名把崩溃聚成簇——这正是"修一个即可消一簇"的工程近似。它刻意跳过 crash 为 null 的记录,并静默跳过损坏/未写完的 JSON(被 kill 的半成品不该中断整个报告),体现了"去重是摘要而非闸门"的设计哲学。此外,CLAUDE.md 还提到一种更强的判据:如果你的流水线会为每个发现生成 PoC 和补丁,那么"某个补丁能否同时解除其他 PoC"就是去重的黄金标准。

严重度规则(Severity)

严重度不按漏洞类别一刀切,而是基于攻击者真正要付出什么才能利用它。验证器先列出前置条件,再把数量映射为分值:

  • 无前置条件 + 未认证远程访问 = High;
  • 一或两条前置条件,或需要认证的路径 = Medium;
  • 三条及以上前置条件,或仅限本地 = Low。

评分前先让模型逐条写出证据(可达性、攻击者控制程度、前置条件、认证要求、读 vs 写、爆炸半径),再给出严重度结论,能有效防止模型被漏洞类别锚定("SQL 注入,所以 critical")而虚高。技能在每次运行开始时还会询问你是否要换成自己的评分标准(HIGH/MED/LOW、CVSS、或你们组织的 bug-bar),因此这套映射只是可替换的默认值。关于每条规则背后的完整推理,可阅读 blog-post.md 的 triage 章节。

规则的两个可调入口

仓库 docs/customizing.md 给出了在不改技能本体的情况下注入组织规则的两个开关:

> /vuln-scan ./src --extra .claude/scan-extras.txt
> /triage ./VULN-FINDINGS.json --fp-rules .claude/fp-rules.txt
  • --extra:向扫描简报追加组织特有的漏洞类别;
  • --fp-rules:向 triage 验证器追加组织特有的误报排除规则,例如"我们到处用 Prisma,原始 SQL 注入不算"、"k8s 资源限制已经覆盖 DoS"。

这类规则文件建议与代码一起纳入版本控制,保证可复现。

如何运行 /triage:命令与参数

技能在 Claude Code 会话内以斜杠命令形式调用,支持管道输出、/vuln-scan 输出与自定义发现文件三种输入形态:

# 直接处理流水线输出
> /triage results/<target>/<timestamp>/ --repo ./path/to/source

# 处理 /vuln-scan 输出
> /triage ./VULN-FINDINGS.json --repo ./path/to/source

# 非交互模式,提高每个发现的验证投票数(默认 3 票)
> /triage ./findings/ --auto --votes 5 --repo ./path/to/source

# 携带组织级误报规则运行(规则说明见 customizing.md)
> /triage ./VULN-FINDINGS.json --repo ./src --fp-rules .claude/fp-rules.txt

各参数说明

  • <findings-path>(位置参数):可以是目录(如 results/<target>/<timestamp>/)或单个 JSON 文件(如 VULN-FINDINGS.json)。
  • --repo ./path/to/source:指向被扫描源码树的根目录,验证器据此做只读的逐条源码核对。
  • --auto:跳过开场访谈,直接使用默认配置。默认行为偏重精确率(precision-biased),即在票数分裂时宁可保守。
  • --votes N:每条发现的验证投票数,默认 3。
  • --fp-rules <file>:追加组织特定的误报排除规则。

开场访谈:运行前必须回答的问题

默认情况下,技能会先"采访"你,收集以下信息,这些答案会直接影响验证与排序:

  1. 信任边界(trust boundary)——哪些输入/调用方被认为是可信的;
  2. 威胁模型(threat model)——你实际关心哪类漏洞;
  3. 评分标准(scoring standard)——HIGH/MED/LOW、CVSS 还是组织 bug-bar;
  4. 分裂票时的倾向——偏重精确率(少报,宁可漏)还是召回率(多报,宁可错)。

访谈机制的价值在 docs/blog-post.md 中有实证支撑:模型在缺乏上下文时最容易虚高严重度——SQL 注入在未认证请求下是 critical,在仅管理员可触发的配置文件中则近乎无影响;上游 WAF 或认证这类补偿控制从源码里根本看不见。给 triage 提供与 discovery 相同的威胁模型(例如明确"我们信任已认证客户端"),就能整类剔除虚高的 critical。若不想逐轮回答,直接加 --auto 即可。

中途中断:断点续跑

/triage 会把分阶段检查点写入 ./.triage-state/(与 TRIAGE.md 相邻),如果运行因上下文耗尽、限流或 Ctrl-C 中断,只需原样重发同一条命令:它会读取 progress.json,从各阶段的 JSON 文件恢复状态,并从未完成的阶段继续,已跑完的子代理不会被重新拉起;传 --fresh 则丢弃检查点从头开始。检查点通过 .claude/skills/_lib/checkpoint.py 原子写入,最终输出 TRIAGE.md 按节追加——因此中途卡住至多丢一个段落,不会丢整个文件。仓库测试 tests/test_skill_checkpoint.py 覆盖了这一机制的保存/加载/分片/完成状态流转。

什么时候该用 /triage(以及什么时候不该)

流水线自身的 grade、judge、dedup 阶段已经应用了上述部分原则,但 /triage 是叠加在它们之上的"跨运行、跨扫描器"图层,且它不挑输入来源——任何发现文件都行。典型的适用场景:

  • 手头刚拿到一摞新鲜发现(新跑的 /vuln-scan 输出);
  • 多次流水线运行的结果互相重叠;
  • 旧工具留下的积压 backlog——docs/blog-post.md 记录了有的团队直接让模型处理历史积压,几天内清掉了数百条陈旧条目。

在这些场景下,/triage 负责把发现验证、折叠、排序,交给下游的是"短、排序、有人负责"的清单,避免告警疲劳(alert fatigue):一旦提交的重复项或虚高严重度太多,产品工程师可能连真正需要立即修复的条目也不看了。

反过来,如果流水线运行持续性地噪音很大,更好的方向是改进流水线本身,而不是反复用 triage 兜底:

  • 确保使用 --stream,让 judge 代理在每条结果落地时充当报告闸门(详见 pipeline.md);
  • 在目标的 config.yaml 里播种 known_bugs,让 find 代理别再收敛到同样的崩溃上(详见 troubleshooting.md 的重复发现一节)。

三份输出工件:TRIAGE.md、TRIAGE.json 与状态目录

一次成功的运行在仓库工作区内留下三类东西:

工件 格式 用途
TRIAGE.md Markdown 人类可读、按优先级排序的发现清单,供工程师直接翻阅
TRIAGE.json JSON 机器可读的发现清单,供 tracker 或下游程序消费
.triage-state/ JSON(含 progress.json 与分阶段文件) 断点续跑的检查点状态

收尾:triage 之后去 patch

triage 输出的"短、排序、有人负责"清单是修复阶段的直接输入:

  • 对流水线产出的崩溃(自带 PoC 与 ASAN trace),使用 bin/vp-sandboxed patch 为每个崩溃生成并验证修复,详见 patching.md;
  • 对没有可运行 PoC 的发现,走 patching.md 的静态模式。

这条链路闭环了完整的安全工作流:/vuln-scan 或 vuln-pipeline 产出发现 → /triage 验证/去重/重排/路由 → /patch 生成候选补丁并独立评审。/triage 的价值正在于它位于这条链路的咽喉位置:它把"扫描能力翻倍"后最稀缺的工程时间,从逐条人工甄别中解放出来,还给真正需要人判断的部分——信任边界与评分标准。

登录后查看全文
defending-code-reference-harness