- AI 插件
- 开发工具
- 插件系统
【免费下载链接】claude-plugins-official
Official, Anthropic-managed directory of high quality Claude Code Plugins.
Claude Security 插件(claude-plugins-official仓库中的 plugins/claude-security)每次扫描交付的最终产物是CLAUDE-SECURITY-RESULTS.md——一份写给人类工程师阅读的 Markdown 安全报告。本文基于仓库内的 report-spec.md 完整解读这份报告从结构、字段到写作纪律的全部规范,并结合 render_report.py、write_scan_meta.py 与 finding.py 等源码,还原报告背后"模型叙述、脚本落盘、投票背书"的验证流水线。读完你将掌握:如何按规范组装一份可被读者快速信任的扫描报告,每个 Coverage 字段对应的底层数据来源,以及严重性/置信度如何被验证面板钳制。
一、报告在交付流水线中的位置:唯一由人阅读的散文
CLAUDE-SECURITY-RESULTS.md是扫描工作流中唯一"以散文形式书写"的产物。报告规范开篇就明确了它的读者画像:拥有这份代码的工程师,很忙,会在约九十秒内决定是否对每条 finding 采取行动。因此报告的全部行文纪律都服务于"快速、可信、可行动"。
报告规范同时划清了书写边界:render_report.py会从findings.json与votes.json生成机器可读的配套产物——CLAUDE-SECURITY-RESULTS.jsonl(每条 finding 一行,字段顺序固定)、CLAUDE-SECURITY-RESULTS.sarif(SARIF 2.1.0 日志)以及CLAUDE-SECURITY-REVISION-<sha12>.json修订戳。规范明确要求:
- 不要手写JSONL、SARIF 或 stamp;
- 不要在报告里复述JSONL 的内容——本文件是给人读的部分。
从源码看,这份分工是硬性的。render_report.py 的render()函数会读取运行目录中的scan-meta.json、findings.json、coverage.json、votes.json,校验每一条记录,然后一次性写出全部机器可读产物(stamp 最后写)。它甚至要求报告 Markdown 文件必须已存在,否则直接拒绝渲染("CLAUDE-SECURITY-RESULTS.md is missing. Write the human-readable report before running this script.")。verification.status是渲染器从投票记录推导出来的,不是由写报告的人声称的——"Never claim a verification status the renderer did not print"。
二、Shape:报告的整体骨架
报告的 Markdown 结构固定为五部分:开篇段落(header)、## Coverage、## Findings、## What was verified、以及贯穿全程的## Rules纪律(规则以 prose 形式存在,不单列章节标题)。整体模板如下:
# Claude Security results <one paragraph: what was scanned (path, revision, mode, scope), when, at what effort, and the headline: how many findings at what severities, or that there were none. Read `revision.dirty` in the run dir's scan-meta.json: on `true`, say the repository's working tree held uncommitted changes or untracked files -- which ones is not recorded, so never name or explain them; on `null`, say the tree state could not be determined; otherwise say nothing of it.> ## Coverage <...> ## Findings ### F1 — <title> (HIGH, confidence medium) ... ## What was verified <...>开篇段落必须一次说清四件事:
- 扫了什么:路径(path)、修订(revision)、模式(mode)、范围(scope);
- 何时、以什么 effort(低/中/高)执行;
- 结论头条:多少个 finding、分别是什么严重级别,或"一个都没有"。
关于revision.dirty的处理有一条非常具体的规则:从运行目录的scan-meta.json中读取revision.dirty——
- 为
true:说明工作树含有未提交更改或未跟踪文件,报告必须如实说明;但"具体是哪些"并未被记录,绝不能点名或解释; - 为
null:说明树状态无法确定,照实说; - 其他情况:保持沉默。
这一字段的采集在 write_scan_meta.py 的worktree_dirty()中实现:通过git status --porcelain --untracked-files=all判断,且会跳过报告目录前缀的路径。扫描时的工作树状态直接影响修订戳文件名(-dirty后缀,见revision_tag()),其目的正是让报告永远与它所描述的代码绑定。
三、Coverage:让读者可以校准一切的开诚布公
Coverage 章节是全报告可信度的根基。规范的原话是:"This section is what makes the rest of the report trustworthy: a reader who knows what you did not look at can calibrate everything else."(一个知道自己没看什么的读者,才能校准其余所有内容。)这一节必须回答"检查了什么、没检查什么、为什么"。
3.1 稀疏检出(sparse checkout)
如果write_scan_meta.py报告了稀疏检出(revision.not_checked_out_dirs保存列表),报告必须说明只扫描了检出部分,并逐一列出未检出的受跟踪顶层目录。源码中由 write_scan_meta.py 的sparse_checkout()检测core.sparseCheckout配置并记录缺失目录。
3.2 验证运行与丢失的候选人
Coverage 必须说明面板(panel)进行了多少次验证运行(coverage.verificationRun),并点名任何从未被验证的候选人及其原因:要么是在通往后续运行的途中丢失(coverage.lostCandidates),要么被交给了未完成的运行。
3.3 未返回的研究员(researchers)
当coverage.researchersReturned低于coverage.researchersDispatched时:
- 说明有多少研究员未返回,以及他们的阅读成果缺失于本次扫描;
- 按
coverage.lostResearchers的每条记录,以其 reading 命名并引用记录的原因; - 条目超过 10 条时改为按原因分组,每组附上研究员被派去读什么;
- 描述未返回原因时只允许引用记录的原因;
- 报告任何"研究覆盖了什么/读了什么"的表述(包括 "What was verified" 一节),只能计入
coverage.returnedReadings中的阅读。
3.4 主动跳过的组件
coverage.skippedComponents的每条记录都带有被略过的路径和 componentizer 的一行原因(vendored、generated、documentation 等)。规范强调:有意跳过的目录是披露(disclosure),不是失败——必须写明原因,而不是让该区域静默消失。
3.5 被修剪的调研镜头(pruned buckets)
如果coverage.prunedBuckets非空,每条记录是某组(或组件)的名称后跟":memory-and-unsafe"——即它没有获得的调研镜头(research lens)。报告必须完整写出每个名字,并直白说明:该组没有任何研究员被问及内存安全类缺陷,无论 tier 如何。
关于 "change boundary" 条目有一条特殊规则:它没有对应的coverage.components行,因为它不是第二组,而是从变更向外工作的研究员,对每个变更文件适用同一规则。给出的只能是工作流自身的理由:在研究员被简报之前,工作流仅凭语言判断(changes/commit 扫描看变更文件的扩展名,codebase 扫描看 inventory 的语言)就认定这些代码全在受托管语言中。不得补充"该镜头不适用于此类代码",也绝不能把剪枝描述成研究员或面板的决定,或"因为已有 finding 而放弃镜头"。没有条目的组则保留了镜头。
3.6 全库扫描的完整性检查
整库扫描要求 inventory 覆盖每个顶层目录——要么被扫描,要么被显式跳过。coverage.completenessCheckOutcome说明检查是否执行:
| 取值 | 含义 | 报告措辞要求 |
|---|---|---|
checked | 检查执行且通过 | 说明整个树都有交代 |
partial | inventory 把一些顶层目录留在两个账本之外 | 逐一列出coverage.unaccountedTopLevelDirs,并直说它们既未被扫描也未被跳过——这正是"无 finding"会夸大覆盖度的地方 |
not-checkable | 目录列表未提供、不可读或为空而 inventory 却命名了子目录(coverage.topLevelRejected说明) | 直说完整性无法检查——这是把"无 finding"从"未检查"变成"干净"的关键 |
not-applicable | diff/commit/范围扫描(目标是变更或范围),或低 effort 且无 inventory 的运行 | 无需多言 |
3.7 inventory 回退(fallback)
若coverage.inventoryFallback被设置,说明 inventory 的分区未被使用,整棵树被当作一个组件阅读(完整但更粗粒度)。原因三选一:
"incomplete-partition":其答案会认可它从未命名的覆盖(跳过了整个目标,或只有爬出树的路径);拒绝项列在coverage.inventoryRejected;"inventory-failed":它没有作答;"empty-partition":它答了个空。
3.8 changes / commit 扫描的特殊规则
对 changes 或 commit 扫描,报告要说"审查的是变更而非仓库":
- 用平实语言命名变更(遵循
jobs/scan-changes.md中关于提交数与分支的规则),而不是用coverage.range里的提交 id; - 按组列出变更文件(
coverage.components;helper 统计的coverage.changedFileCount个文件中的coverage.diffFiles个、coverage.diffLines行); - 报告派出多少研究员、返回多少(
coverage.researchersDispatched/coverage.researchersReturned),以及被要求投入的 effort(coverage.researchEffort——是"被要求"而非"实测",模型运行时可能自行调整); - 明确声明:变更未参与其中的缺陷不属于本次审查范围,留给 codebase 扫描;
- 列出
coverage.preExisting的每条记录:面板判定为真实但早于变更的候选人; - 当基线是分支自身的已推送副本(diff 只含未推送提交)时,说明已推送提交不在此次审查内;
coverage.outsideScope非空时,说明这些 finding(它命名了它们)在请求范围之外,因变更触及它们而报告;coverage.changedFilesRejected被设置时:引用记录的值,说明变更被作为一个组审查、其研究员自行列出了文件;若coverage.changedFilesMiscounted也被设置,给出两个数字、说明审查了哪个列表(其reviewed字段)以及那是多少文件(coverage.diffFiles)。
3.9 折叠形状与范围大小被拒
coverage.collapsed为"small-scope":说明中等 effort 的小范围(给出coverage.scopeFiles)折叠成了成比例的单研究员形态——一次快速定向扫描,仍经面板验证,但不是穷尽阅读;coverage.scopeSizeRejected:引用记录值,说明其对实际运行 tier 的后果——medium 下范围未被当作小范围处理,因此跑的是完整流水线而非快速路径,且空范围无法被短路。
3.10 运行规模的报告
当coverage.targetComponents被设置时,说明运行如何被定大小:目标约有coverage.targetFiles个受跟踪文件、约coverage.targetComponents个组件(每个约coverage.filesPerComponent个文件,或当目标持有的组件数超过coverage.componentCap时更大),最多保留coverage.componentCap个。
3.11 研究员自己的"未读声明"与对照账
最后,报告要转述研究员自己报告没读过的东西——作为他们的陈述而非事实:
coverage.research.components:按组件列出其研究员声明未触及的路径及原因——按目录归纳成一行(背景树——vendored 树、焦点下的测试与 fixture 树被留作背景——是预期中的背景而非缺口),单个文件只在每组件少量时点名;coverage.research.tree(存在时):把该陈述与组件内受跟踪文件对照——多少文件读到了结论、多少位于声明未触及的路径下、多少没有任何研究员交代(coverage.research.capped为 true 时账目被截断,即至少读了这么多、至多有这么多未交代)——并列出coverage.research.tree.unaccountedPaths(列前几个,总数是全部),因为一个没人读完的组件绝不能冒充干净的组件;所有组件之外的文件(outsideComponents)是本小节已点名的跳过/丢弃区域加上无组件认领的根文件,不是研究员的缺口;- 当
coverage.research为 null 或缺少 tree 时,该检查未运行,对此只字不提(但已声明的路径仍然成立)。
这些数据在渲染器里被整理成run_shape(render_report.py),并同步反映到 SARIF 的通知(notifications)中。
四、Findings:每条发现的八段式模板
## Findings章节中,每条 finding 的标题里的F<n>是它在findings.json中的id,逐字照抄——finding 到达时已处于报告顺序,因此绝不重新编号、不重排、不发明 id。编号中的缺口是面板未在该 id 下保留的候选人;只有当完整面板驳斥它时才可称其为"被拒",绝不能因为coverage.adversarialCasualties、coverage.preExisting或coverage.lostCandidates(按候选人 id)点名它就那样说。
每条 finding 的模板:
### F1 — <title> (HIGH, confidence medium) **Impact.** <what an attacker gets. Lead with this: it is what decides priority.> **Where.** `path/to/file.py:123` in `function_name` — <cwe_id, then "(also <other_cwe_ids>)" when the finding carries further CWEs> **Link to the change.** <a changes or commit scan only: the finding's via_change, the changed line that takes part in the attack and how, named by file:line and never quoted; omit the line for a scan of the codebase> **What.** <the vulnerability, in two or three sentences. Name the untrusted source, the dangerous operation, and why nothing in between stops it.> **Exploit scenario.** <a concrete walk-through. Not "an attacker could inject SQL" -- what they send, what happens, what they get.> **Preconditions.** <bullets: what must be true. Authentication? A non-default config? Victim interaction? An empty list means none, which is worth saying.> **Fix.** <what to change, in outcome terms. The root cause at the sink, not a patch at one caller.> **Verification.** <n>/3 lens verifiers confirmed.各字段的写作要求:
- Impact:攻击者能得到什么。必须放在最前——它决定优先级;
- Where:
文件:行加函数名,后跟主 CWE id;携带更多 CWE 时追加(also <other_cwe_ids>); - Link to the change:仅 changes/commit 扫描使用,取 finding 的
via_change,点名参与攻击的变更行及其方式(以file:line命名,从不引用原文);codebase 扫描省略此行。从 finding.py 可见via_change以file:line开头、由LINK_SITE正则识别并规范化; - What:两到三句话说明漏洞——点出不受信任的来源、危险操作,以及中间为什么没有任何东西阻止它;
- Exploit scenario:具体走一遍——不是"攻击者可注入 SQL",而是"他们发送什么、发生什么、得到什么";
- Preconditions:必须为真的条件列表(认证?非默认配置?受害者交互?)——空列表意味着无前置条件,这一点值得明说;
- Fix:以结果导向说明改什么——针对 sink 处的根因,而非某个调用点的补丁;
- Verification:
<n>/3个镜头验证者确认。
F2 — ...依此类推。验证者计数对应源码中的固定面板规模:PANEL_VOTER_COUNT = 3、PANEL_KEEP_QUORUM = 2(见 finding.py),即每条 finding 由三名验证者组成的对抗性面板审查、2/3 通过才保留。
五、What was verified:验证状态必须如实交代
## What was verified用一段话说明:产出这些 finding 的流水线、每条 finding 通过的投票、以及 stamp 的verification.status。
- 状态不是
"verified"时:用平实语言解释其含义和应对措施,不得掩盖; - 状态是
"verified"但存在未返回的研究员时:说明"verified"不覆盖缺失的阅读。
verification.status的推导逻辑在 render_report.py 的verification_summary()中,它只会在每一项都满足时给出"verified",否则给出"unverified"并附带机器可读的reason_kind。源码中可见的失败原因种类包括:
| reason_kind | 触发条件 |
|---|---|
no-vote-record | 运行目录没有 votes.json 或它不是扫描工作流的记录 |
no-candidate-count | votes.json 缺candidates字段 |
nothing-examined | 派出了研究员但一个都没返回 |
finding-panel-incomplete | 有 finding 缺少完整 3 票面板轮次 |
finding-below-quorum | 报告的 finding 中有未达保留法定人数的 |
candidates-not-paneled | 记录了候选人但无一入面板 |
no-panel-completed | 派发了面板轮次但无一完成完整 3 票审查 |
candidate-panel-incomplete | 有候选人未经完整面板轮次即被丢弃 |
continuation-incomplete | 有候选人被交给未完成的验证运行 |
findings-refused | 渲染时被拒绝的 finding 缺席报告 |
这套枚举证明了一个关键设计:报告对自己严谨程度的说明是由代码计算出来的,而非产生 finding 的模型所声称的(README 中亦明示:"the record of how thoroughly a run was verified is computed in code rather than asserted by the model that produced the findings")。
六、Rules:贯穿全篇的写作纪律
6.1 严重性是可利用性与影响,不是置信度
- CRITICAL:严重影响且攻击者面前毫无阻碍;
- HIGH:严重影响但存在一个真实障碍;
- MEDIUM:影响有界,或严重影响但需多个条件;
- LOW:影响有限且利用苛刻。
findings.json中的严重性是最终裁决:当面板的确认投票者将 finding 评为低于研究员所评的级别时,工作流已将其调低,coverage.severityLowered会点名每条此类 finding 并给出两个评级——报告必须在它的Verification.行说明这一点,不得恢复被调低的严重性。
不确定性与confidence无关,而是置信度词:low、medium、high,由面板投票钳制——只有全票通过的面板才配得上high,工作流已把其他任何high调低;若报告者擅自提高,render_report.py会再把它降回来。这一钳制在源码vote_confidence_ceiling()(finding.py)中有精确对应:面板完整时,只有true >= PANEL_VOTER_COUNT(全票)才是high,否则是medium。
6.2 排序与分批
- 先按严重性、再按置信度排序——读者会中途停下,把最重要的放最上面;
- 超过二十条 finding 分批写:一次调用容纳全部 finding 可能超出模型输出上限,被截断的 Write 会被整体拒绝。因此第一个 Write 包含开篇段落、Coverage、前二十条 finding 和 What was verified,之后每二十条用 Edit 插入到
## What was verified标题之前。
6.3 每个 finding 必须引用真实的 file:line
指向错误行的 finding 比漏报更伤人——读者在追查时会失去对报告其余部分的信任。渲染器为此做了路径校验:file_field()/relative_path()会拒绝任何逃出仓库或指向缺失文件的路径(finding.py),无法承载路径的 finding 会被点名拒绝(refused <id>)并从产物中剔除,stamp 相应标记为未验证。
6.4 硬编码凭据 finding 的特殊规则
任何携带 CWE-798、CWE-259、CWE-321 或 CWE-671(在cwe_id或other_cwe_ids中)的 finding,不写 "Link to the change" 行——它本要点名的变更行就是凭据本身,而 JSONL 和 SARIF 已经收回了该链接。README 中亦说明:凭据 finding 的文件、行号与符号仍会定位,但绝不引用凭据所在源码行。
6.5 无控制字符
只允许\n和\t。报告在终端中被阅读,转义序列可能改写人所见的内容。若扫描源码中确实出现此类字节,描述它而非复现它。
6.6 不模糊、不填充
不要为了对代码客气而软化真实 finding,也不要为了显得周全而夸大细枝末节。"No findings" 本身就是一份完整的报告——把它写好(覆盖了什么、没覆盖什么)比一整页"可能"更有价值。
6.7 研究者的镜头不是面板的
研究者的镜头(lens)是一个漏洞类别(其组保留的类别之一或全部);面板投票者的镜头是可达性(reachability)、影响(impact)或防御(defenses)。绝不可把面板的三个当作研究者的三个。
6.8 从不声称运行过什么
扫描不执行仓库代码:没有运行过测试、没有发射过 exploit、没有验证过 PoC。每条 finding 都源自阅读。报告必须这样说,而不是暗示一次演示。这与插件整体的信任模型一致——仓库内容是"被审查的数据",永不是指令。
七、写作质量的标杆:Example of the bar
规范用一个对照展示了合格与不合格的 finding 写法。不要这样写:
The code may be vulnerable to SQL injection. Consider using parameterized queries as a best practice.
要这样写:
Impact.Any unauthenticated caller of
GET /users?name=can read every row of theuserstable, including password hashes and email addresses.Where.
api/app.py:3inget_user— CWE-89What.
namearrives from the query string inhandlers.py:41and is interpolated into the SQL string with%. No escaping or validation runs on the path between them; thevalidate_namecall inhandlers.py:38checks length only.Exploit scenario.
GET /users?name=' OR '1'='1makes the WHERE clause tautological and returns the full table in the JSON response.
对比可见:合格示例把"可能、请考虑"的模糊建议替换为可验证的具体事实链——来源(handlers.py:41的 query string)、危险操作(%插值)、缺失的防线(validate_name只查长度)、以及一条可复现的 exploit 路径。
八、从规范到实现:报告背后的验证闭环
将 report-spec 与仓库源码对照,可以还原出整条验证闭环:
- 扫描元数据:
write_scan_meta.py从 git 自身捕获修订(commit、branch、dirty 状态)、顶层目录、文件计数,写入scan-meta.json——stamp 绝不依赖任何人转写的值(write_scan_meta.py); - 研究工作流:
claude-security:scanworkflow 派出研究员并按 tier 组织(low 单研究员、medium 组件×类别、high 双研究员),最终一律进入固定的三票验证面板; - 投票记录:面板的投票进入
votes.json,verification_summary()据此计算状态与各计数(render_report.py); - 去重与落盘:
one_per_site()把同一 site(规则×文件×行)的多个 finding 合并为最强一条,其余在产物中披露合并句(render_report.py);SARIF/JSONL/stamp 全部由脚本写出; - 清洗与交付:交付后
scan-redactor把产物中的凭据值替换为[REDACTED](这是模型的最佳努力而非保证),随后运行目录被整体移除,报告目录只留下用户阅读的产物。
这套设计在 patch-spec.md 中形成了对称结构:修复工作同样由人写工作记录(patches.json)、脚本渲染产物(F<n>.patch等),"no diff byte and no confidence claim is ever re-typed by a model on its way to the user"(没有任何 diff 字节或置信度声明在通往用户的路上被模型重新转写)。报告规范与补丁规范共同构成了 Claude Security 插件的契约层:模型叙述与决策,脚本落盘与验证,人类阅读与行动。
对于阅读或维护此类报告的开发者而言,最值得带走的三条原则是:报告的可信度取决于它如何交代"没看什么"(Coverage);每条 finding 的优先级由 Impact 与可复现的 Exploit scenario 决定,而不是措辞的轻重;而"这份报告有多可靠"这件事,永远以render_report.py盖下的verification.status为准。
- AI 插件
- 开发工具
- 插件系统
【免费下载链接】claude-plugins-official
Official, Anthropic-managed directory of high quality Claude Code Plugins.
相关推荐
security-audit skill 验证与报告流水线深度解析:从候选漏洞到机器可读的 findings.json 与审计报告
security audit skill 验证与报告流水线深度解析:从候选漏洞到机器可读的 findings.json 与审计报告 本篇技术指南围绕 VALID
AI 技能应用安全Claude Code Haha 文档编写规范:docs/AGENTS.md 全解与站点流水线实践
Claude Code Haha 文档编写规范:docs/AGENTS.md 全解与站点流水线实践 导读:本文面向在 Claude Code Haha 仓库中新
人工智能AI 应用桌面应用代码智能体MCP Clientswewe-rss微信公众号RSS生成完整指南
wewe rss微信公众号RSS生成完整指南 微信公众号文章藏在微信的会话列表里:既导不进 RSS 阅读器,也没有全文输出。wewe rss 是一个可私有化部署
AI 插件开发工具插件系统
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考