安全审查的产出,为什么总是归不了档
团队里让 AI coding agent 参与代码安全审查,通常会卡在一个地方:产出是散文。
散文没法入库、没法跨项目比对、没法在两次审查之间做增量。半年后有人问"这个模块上次审过吗、当时结论是什么、依据在哪",答案往往是某个人的记忆,或者一份没人再打开的报告。
Cloudflare 开源的 security-audit-skill 提供的是另一种形态:审计产出被定义成结构化记录,并由两个零依赖校验脚本当门禁。README 对它的定位是:"A coding-agent skill that turns your agent into a security auditor."(一个把 coding agent 变成安全审计员的 skill。)它在第三方聚合站 2026-09-21 那期 GitHub 周榜总榜排第三(+9,047 星,聚合口径,非 GitHub 官方榜单)。
这篇记录我在一台接百炼的 coding agent 上跑完一轮受限审计后,从企业落地角度整理出的三件事:结构化产物为什么适合治理、怎么把它接进 CI、机器与人工的分工线画在哪。
一轮完整验收:底座、安装、靶仓、quick 档
skill 不绑定宿主,README 对底座只有一条逐字要求:"A coding agent with a model that supports tool use and parallel sub-agents"(一个模型支持工具调用和并行子代理的 coding agent)。
我的环境核验结果:
| 项目 | 本机取值 | 是否满足要求 |
|---|---|---|
| 宿主 agent | Qwen Code | skill 不绑宿主 |
| 模型 | qwen3.8-max | 在百炼模型目录内 |
| baseUrl | dashscope 兼容端点(compatible-mode) | 成立 |
| 工具调用 | qwen3.8-max 支持 function calling | 满足 tool use 要求 |
| 并行子代理 | 本次单 agent 串行完成 | 未实测 |
安装命令按 README 原文:
npx skills add https://github.com/cloudflare/security-audit-skill --skill security-audit
我实测时是把 skill 目录落盘到工作区的 agent skills 目录,效果等价。Key 在控制台密钥管理页创建(地域选华北2 北京),命令文档与模型清单见阿里云百炼 CLI(bl)。
靶仓是本机自造的演示仓库:两个文件,三个预埋漏洞——查询参数拼进 eval()、源码里硬编码 live key、上传文件名不做净化直接拼路径。没有审计任何真实项目。
run profile 选 quick(skill 为小目标定义的受限路径:粗粒度台账单元、单波 hunter、单次 critic pass),阶段 1 到 4 实跑,阶段 5(独立记录复核)与阶段 6(中立报告)未跑。
结构化产物为什么适合做治理
这套 skill 的产物有两份 JSON:覆盖台账 coverage-ledger.json 与发现记录 findings.json。它们能被治理流程接住,靠的是三个设计。
一是身份稳定。 coverage_id 不是随手起的名字,而是由 surface、boundary、subsystem、attack_class 四个引用做百分号编码后用 :: 拼出的规范 ID。同一个"表面×边界×子系统×攻击类"组合,无论谁跑、什么时候跑,ID 都一致。这是跨项目、跨周期对齐的前提。
二是增量可延续。 README 把多次运行定义为 additive:先读旧台账与旧发现,变更过的源码重新验,没变的证据带着 fingerprint 延续。对治理台账来说,这意味着每轮审查不必从零重建,历史记录也不会被覆盖掉。
三是证据可核查。 findings 的 trace 每条带 kind(entrypoint / propagation / sink)、file、line、scope、description;evidence 锚到 file+line;execution 要有 attacker_perspective、payloads、instructions、observed_result;severity 与 confidence 是带 score 和 reason 的对象,另有字符串枚举的 overall_severity;数组按 fingerprint 字典序排。
三个字段族对应三种治理需求:trace 供人复核路径,execution 供人复现验证,severity/confidence 的 reason 供风险决策留痕。顺序确定还带来一个附带好处——两份产物可以直接 diff。
本轮三个发现都配了可复现 PoC,命令与输出均为本机真跑:
node -e PoC1 # eval 注入:input "' || '1'=='1" => returned rows: 1(应为 0)
node -e PoC2 # 路径穿越:resolve 后落到 /etc/passwd
grep -n sk-live src/server.js # 3:const API_KEY = 'sk-live-…';
校验脚本就是现成的 CI 门禁
企业侧最可以直接搬走的是校验时机。README 原文:"The parent runs validate-coverage-ledger.cjs after creating the ledger and after each later ledger update. It runs validate-findings.cjs in Phase 4 and again after every Phase 5 replacement."
拆成流水线上的三个卡点:
| 卡点位置 | 校验对象 | 不通过的处置 |
|---|---|---|
| 台账创建后、每次台账更新后 | coverage-ledger.json | 狩猎阶段不开始,流水线停在这一步 |
| 阶段 4 | findings.json | 发现不进报告,不写入治理台账 |
| 阶段 5 每次替换后 | findings.json | 复核改动同样受检,避免复核成为旁路 |
第三行对企业尤其重要:独立复核会替换发现,如果替换后的记录不再过一遍 schema,最需要被校验的改动恰好绕过了校验。
校验脚本零依赖、Node 直接跑,接进流水线的成本接近于跑一个单元测试。我这一轮的真实迭代记录可以直接当"预期节奏"参考:
| 产物 | 被拒次数 | 通过时的输出 |
|---|---|---|
| coverage-ledger.json | 3 次(顶层类型 → 缺 14 个必填字段 → 规范 ID 与状态约束) | PASS: 3 coverage units valid |
| findings.json | 4 次(trace/evidence 结构 → 枚举与数组类型 → 缺 overall_severity 与排序 → overall_severity 须为字符串枚举) | PASS: 3 findings valid |
合计 7 次拒绝,拒因全部是结构契约,没有一次是"没看出漏洞"。这个分布对团队的意义是:agent 在这套流程里的主要失败模式可以靠脚本兜住,不需要靠人反复叮嘱。
接入治理台账的动线
把上面两部分串起来,一条可落地的动线是:
- 准入:确认宿主模型支持工具调用(本次为 qwen3.8-max,支持 function calling),skill 按 README 命令安装。
- 首审:quick 档跑阶段 1-4,两份 JSON 过校验脚本,产物带日期与环境标识归档。
- 入库:把通过校验的 findings 按 fingerprint 写入治理台账,coverage_id 作为跨轮次对齐的主键。
- 增量:源码有变更时再跑一轮,按 additive 语义合并——变更部分重新验,未变证据带 fingerprint 延续。
- 人工环节:阶段 5 独立复核与风险定级由人接手,复核产生的替换记录再过一次 validate-findings.cjs。
第 5 步在本次实测中没有跑,属于流程设计而非验证过的结论。
验收标准表
要把这件事写进团队规范,下面这几条可以直接抄:
| 验收项 | 标准 | 本次实测状态 |
|---|---|---|
| 底座能力 | 模型支持工具调用;并行子代理能力按需评估 | tool use 满足;并行编排未实测 |
| 台账结构 | validate-coverage-ledger.cjs 输出 PASS | 达成(v4,3 个覆盖单元) |
| 证据锚定 | 每条发现的 evidence 锚到 file+line,execution 含 observed_result | 达成(3 条发现均有 PoC) |
| 发现结构 | validate-findings.cjs 输出 PASS,数组按 fingerprint 字典序 | 达成(v5,3 条发现) |
| 产物归档 | 两份 JSON 带日期、环境、profile 标识入库 | 本轮产出两份 JSON 且均通过校验;入库标识是建议做法,非本轮实测项 |
| 独立复核 | 阶段 5 完成并重新过校验 | 未跑 |
机器与人工的分工
结构化产物带来的另一个好处,是分工线可以画得很清楚:
| 环节 | 机器(agent + 校验脚本) | 人工 |
|---|---|---|
| 覆盖划分 | 按台账 schema 生成覆盖单元与规范 ID | 审覆盖是否漏了关键业务面 |
| 漏洞狩猎 | 按覆盖单元搜索、给出候选 | 判断候选是否属于业务可接受风险 |
| 证据固化 | 产出 trace、evidence、execution 字段 | 抽查 PoC 是否真能复现 |
| 结构合规 | 校验脚本强制 schema,不过则退回 | 不介入 |
| 定级 | 给出 severity/confidence 的 score 与 reason | 结合业务影响做风险决策 |
| 独立复核 | 阶段 5 的记录替换仍需过校验 | 复核结论由人签署 |
| 修复验收 | 重跑一轮,按 additive 合并结果 | 确认修复是否符合安全要求 |
分工的判断标准是:凡是能被 schema 表达、能被脚本判定的,交给机器并当门禁;凡是需要业务上下文和责任归属的,留给人,并要求人在结构化记录上签署,而不是在散文报告上签署。
边界
- 本次跑的是 quick 受限档,不是全量审计;结论只覆盖这个靶仓这一次运行
- 阶段 5、6 未跑:独立记录复核与中立报告这轮没有产出,相关动线属于设计而非实测
- 三个漏洞是本机自埋靶子,不代表任何真实项目的安全状态
- 周榜 +9,047 为第三方聚合站口径,非 GitHub 官方榜单
- skill 要求 tool use 加并行子代理;本次在单 agent 上串行完成 hunter 与 verifier 角色,并行编排未实测
本文只做流程与产物形态的验收,不含任何模型能力评测,也不涉及安全产品之间的对比结论。
上手
npx skills add https://github.com/cloudflare/security-audit-skill --skill security-audit
装完先做一件小事:找一个自己敢埋洞的小仓库当靶子,quick 档跑一轮阶段 1-4,数数校验脚本拒你几次,再看通过后的两份 JSON 长什么样。这两份产物的字段结构,就是你们治理台账可以直接对齐的 schema。
模型底座不需要换工具链,接百炼的 coding agent 即可。Key 在控制台密钥管理页创建,命令文档与安装说明见阿里云百炼 CLI(bl),模型清单可在阿里云百炼首页浏览。