让编码 Agent 给自家仓库做一遍安全审计,真正难的不是「让它找漏洞」,而是「怎么判断它找到的到底是不是真的」。多数人的做法是跑一遍扫描,拿到几十条煞有介事的结论,然后发现里面混着重复项、误报,以及一堆无法复现的猜测。Cloudflare 开源的 security-audit 技能把这件事拆成了六阶段流水线,并在流程里硬编码了两条关键约束:找漏洞的 Agent 和验证漏洞的 Agent 必须不是同一个;所有结论必须落到带 JSON Schema 校验的机器可读文件里,再由另一批全新 Agent 独立复核其中的源码引用。它还有一个覆盖率账本,把「哪些代码单元已经查过、当时查了什么」显式记录下来,让多次运行可以累加覆盖而不是重复劳动。官方说得很实在:在他们的测试运行里,单次运行大约只能发现多轮累计漏洞的一半。本教程六步落地:装技能、分清两种模式与输出目录、跑侦察并生成覆盖率账本、按账本分配猎捕者、做证伪与结构化输出、最后独立复核并产出报告。
Step 1:装技能:一条命令,装到项目里或装到用户级
技能通过 Skills CLI 安装。装到当前项目就直接用;加 --global 则装到用户级,之后任何项目都能触发。安装后不需要手写配置去注册,技能的触发由它自己的描述匹配请求。想知道 CLI 还有哪些选项,比如非交互式安装或选择具体 Agent,跑一次帮助即可。
# 装到当前项目
npx skills add https://github.com/cloudflare/security-audit-skill \
--skill security-audit
# 装到用户级(任何项目都能用)
npx skills add https://github.com/cloudflare/security-audit-skill \
--skill security-audit \
--global
# 查看 CLI 支持的选项(Agent 选择、非交互等)
npx skills --help
装完之后可以在目录里确认技能文件齐全:核心是 SKILL.md(设置、核心原则、平台术语、流程总览与审计反模式),加上按攻击面划分的若干份提示词文件,以及两个零依赖的校验脚本和它们的测试。这份文件清单本身就是一张「审计会覆盖哪些面」的地图,值得扫一眼。
技能目录主要文件与分工
SKILL.md 设置、核心原则、平台术语、流程总览、审计反模式
RECONNAISSANCE.md 阶段 1 侦察提示词与综合指令
HUNTING.md 阶段 2 编排、猎捕方法论、验证规则
ATTACK-CLASSES.md 核心类、通配类、显而易见类攻击提示词
MEMORY-SAFETY-AND-BINARY.md 内存安全、二进制、内核(原生目标)
AI-AND-LLM.md 提示词注入、Agent 与工具、输出处理
WEB-PROTOCOL-AND-AUTH.md HTTP 请求分帧、缓存、认证协议
CLIENT-SIDE.md DOM 注入、消息信任、UI 伪装、原型污染
SUPPLY-CHAIN-AND-RELEASE.md 依赖、CI、发布、签名、更新、插件、扩展
CLOUD-AND-DEPLOYMENT.md IAM、基础设施即代码、容器、无服务器、入口、运行时配置
PROTOCOLS-RPC-AND-MESSAGING.md RPC、序列化、队列、Broker、Webhook、流式协议
RESOURCE-EXHAUSTION-AND-AVAILABILITY.md 共享资源、配额、队列、Worker、运营者支出
DATA-ISOLATION-AND-LIFECYCLE.md 租户隔离、缓存、搜索、导出、备份、迁移、删除、恢复
DESKTOP-MOBILE-AND-LOCAL-IPC.md 原生应用、深链、WebView、导出组件、守护进程、本地 IPC
VALIDATION-AND-REPORTING.md 阶段 3-6 证伪、结构化输出、记录复核、报告
report-schema.json 三类结论的 JSON Schema
validate-findings.cjs findings.json 校验器(零依赖)
validate-coverage-ledger.cjs coverage-ledger.json 校验器(零依赖)
Step 2:分清两种模式:整轮审计出报告,还是只问安全建议
技能在请求命中它的触发词时自动激活,触发词包括 security audit、find vulnerabilities、pen-test the code 这类说法。但命中触发不等于走完整流程:直接的整仓审计或渗透测试请求会走 full audit mode;而安全提问、聚焦某处漏洞的改动,默认走 guidance mode,除非你明确要求产出报告工件。这个区分很重要——它决定了你是在等一份报告,还是在要一句建议。
# 整轮审计模式(会产出完整报告工件)
security audit this codebase
# 聚焦某个目录
find security vulnerabilities in ./src
# 指定输出目录
do a security review, output to ~/audits/my-project
输出位置有个默认约定值得记住:在 full audit mode 下,如果不指定输出目录,结果会落到用户主目录下的 security-audit-skill 里,按仓库名加运行序号组织,大约是 ~/security-audit-skill/仓库名/run-N 这样的形态。另外,流程只在你显式指定了一个被版本控制忽略的目录时,才会往目标仓库内部写东西。这条边界保证了审计产物不会污染你的工作区。
别让审计产物进 Git:如果你决定把输出目录放在仓库里,务必确认它在 .gitignore 中。审计产物里可能包含未修复漏洞的详细描述与源码位置,推到公开仓库等于给自己提前发了通告。
Step 3:阶段 1-2:先画地图,再记账本
阶段 1 是侦察:把架构、信任边界、输入面、已有证据,以及可确定性覆盖的范围,整理进 architecture.md 与 coverage-ledger.json 两份文件。关键在第二份——覆盖率账本把代码切成可枚举的单元,并记录每个单元是否已被检查、由谁检查、用了什么方法。它让「查完了没有」这个问题从此有了可验证的答案,而不是靠 Agent 自己说「我查过了」。
阶段 1 侦察产出
architecture.md 架构、信任边界、输入面、已有证据
coverage-ledger.json 可覆盖单元清单 + 每个单元的检查记录
父流程在创建账本之后、每次账本更新之后,
都会跑一次 validate-coverage-ledger.cjs
阶段 2 是覆盖率驱动的猎捕:父流程从账本单元出发分配彼此隔离的猎捕者(hunter),每个猎捕者记录自己的检查动作,另有覆盖率评审者(coverage critic)专门找账本里的空白。这一步的设计意图很清晰——猎捕由覆盖率驱动,而不是由灵感驱动,也就不会出现「同一个热门文件被查了八遍,冷门模块一遍没动」的情况。
沙箱缺失的后果:工作流要求一个操作系统强制的沙箱来跑目标控制的构建、测试、进程、浏览器、模拟器、模糊测试与夹具,且必须断外网、使用经过清理的白名单环境、设置资源上限、只允许写入指定的临时路径。没有这些控制时,流程不会硬跑目标代码,而是把该线索保持为 needs_validation。这不是报错,是设计上的安全降级。
Step 4:阶段 2 的攻击面:按目标类型挑提示词文件
猎捕阶段可用的攻击面文件有十几份,按目标类型划分。实践里不必全开:一个纯前端仓库去跑内核内存安全提示词,只会换来一堆噪音。按项目形态挑对应的文件,命中率会高得多。下面这张对照表可以直接拿去用。
项目形态 优先加载的攻击面文件
---------------------------------------------------------------
Web 后端 / API WEB-PROTOCOL-AND-AUTH、DATA-ISOLATION-AND-LIFECYCLE
RESOURCE-EXHAUSTION-AND-AVAILABILITY
浏览器前端 CLIENT-SIDE、WEB-PROTOCOL-AND-AUTH
大模型应用 / 智能体 AI-AND-LLM、SUPPLY-CHAIN-AND-RELEASE
原生 / 系统 / 二进制 MEMORY-SAFETY-AND-BINARY、DESKTOP-MOBILE-AND-LOCAL-IPC
云上部署 / IaC CLOUD-AND-DEPLOYMENT、SUPPLY-CHAIN-AND-RELEASE
消息 / RPC / 队列 PROTOCOLS-RPC-AND-MESSAGING
任何项目 ATTACK-CLASSES(核心类 + 通配类 + 显而易见类)
其中「显而易见类」这一组容易被忽略,但它往往是投入产出比最高的:默认凭据、未关闭的调试端点、硬编码密钥、缺失的速率限制。这些不需要复杂推理,只需要有人真的去看一眼。让 Agent 先跑这一组,通常能在几分钟内拿到可用的结果。
Step 5:阶段 3-4:让另一个 Agent 去证伪,再写进带 schema 的文件
阶段 3 是候选验证:每一个独立候选,都交给一个全新的验证者(verifier),而它的任务是证伪,不是确认。这是整个流程里最反直觉也最关键的一步——找漏洞的 Agent 天然倾向于证明自己是对的,换个没参与过的 Agent 去挑毛病,误报率才会真的降下来。阶段 4 把结果写成三类结论,并用零依赖脚本对 findings.json 做 schema 校验。
三类结论的判定门槛
confirmed 有完整的源码追溯链 + 有边界的可观测结果
needs_validation 有一个明确的未决事实,且不带严重级别
rejected 候选已被证伪,同样记录在案
阶段 4 跑 validate-findings.cjs
阶段 5 每次记录替换之后再跑一次
三档的门槛差异值得逐字理解。confirmed 要求两件事同时成立:能一路追溯到具体源码,且观测到的结果是有边界的——不是「可能有问题」,而是「在这里输入这个,会得到这个」。needs_validation 只允许保留一个确切的未决事实,并且明确不带严重级别:它是「我卡在这儿了,需要人来判断」,不是「我猜这里有问题,大概中危」。rejected 也要记录,因为不记录就等于下一轮还会再查一遍。
# 想自己复核结论,直接跑校验器(零依赖,只需 Node)
node validate-findings.cjs findings.json
node validate-coverage-ledger.cjs coverage-ledger.json
# 仓库里还带了两个校验器各自的测试与生产者兼容的夹具检查
node validate-findings.test.cjs
node validate-coverage-ledger.test.cjs
两条容易被误用的判定原则:其一,「严重级别需要影响」——是可能性乘以影响,而不是偏离检查清单的程度。其二,「纵深防御的缺口不是漏洞」——如果 A 层已经挡住了攻击,B 层的缺失只是加固建议,不是漏洞。把这两条丢掉,报告里就会挤满无法排期的噪音。
Step 6:阶段 5-6:独立复核源码引用,然后产出三份报告
阶段 5 是独立记录复核:全新的一批 Agent 去核对最终结论里的源码引用是否属实,凡是发生过实质性替换的记录,还要再过一遍独立验证。这一步对付的是「大模型的引用幻觉」——结论写得漂亮,但引用的函数根本不存在。阶段 6 由已核实的记录与覆盖率账本推导出三份报告:REPORT.md、FINDINGS-DETAIL.md 与 NEEDS-VALIDATION.md,且报告对目标保持中立表述。
最终产物
REPORT.md 从已核实记录与覆盖率账本推导的主报告
FINDINGS-DETAIL.md 已确认问题的细节
NEEDS-VALIDATION.md 需要人来判断的未决事实清单
多次运行同一个仓库是累加的:
沿用上次的账本与结论
针对性补覆盖空白
对变更过的源码重新验证
沿用仍有效的当前源码证据
不把过时的、未决的工作当作已覆盖
「累加」这一条决定了这个技能该怎么用。既然官方自己测出单次运行大约只发现多轮累计漏洞的一半,那么正确用法就不是「跑一次,看报告,收工」,而是「预算之内跑多轮,每轮瞄着账本里的空白」。团队里更实际的排法是:把整轮审计挂在月度或发布前的节奏上,而不是等出事了才跑。
它是起点,不是平台:官方明说这个技能是 Cloudflare 内部那套漏洞发现 harness 的单仓库起点,后者已经长成了多阶段、舰队级的系统。也就是说,别指望它替你做持续监控、跨仓对比或工单流转。想做那些,得自己在它之上接。
常见问题 FAQ
| 问题 | 结论 |
|---|---|
| 一定要有沙箱吗? | 如果要执行目标代码,必须有断外网、白名单环境、资源上限、只写指定临时路径的操作系统级沙箱。没有就停在 needs_validation,流程不会硬跑。 |
| 单个运行能查全吗? | 不能。官方测试里单轮大约只发现多轮累计漏洞的一半,正确用法是按账本空白多轮累加。 |
| 结论能直接进工单系统吗? | 能。findings.json 有 report-schema.json 约束,且两个校验器零依赖,可以挂在 CI 里跑。 |
| 防御深度的缺失算漏洞吗? | 不算。A 层已挡住攻击时,B 层缺失只是加固建议。这条原则在 SKILL.md 里被明确写成判定规则。 |
| 报告会写进我的仓库吗? | 默认不会。未指定输出目录时落到主目录下的 security-audit-skill/仓库名/run-N;只有你显式指定了一个被版本控制忽略的目录才会写进仓库。 |