让 AI 帮你审一遍代码的安全问题,最怕两件事:一是它只看了几个文件就说「没问题」,二是它把一堆似是而非的问题全报成高危。Cloudflare 6 月在博客 Build your own vulnerability harness 里开源了一个 skill:security-audit。按 README 的说法,它是 Cloudflare 漏洞挖掘系统最早的起点,后来才长成覆盖整个代码库群的流水线。
这个 skill 的做法是把审计拆给多个互相隔离的 Agent:有人侦察,有人按攻击类别找漏洞,有人专门查漏了哪里没看,每个候选漏洞再交给一个没参与过的新 Agent 去反驳。我们用本站 API Key 在 Claude Code 里跑了两个目标,一个故意留漏洞,一个是成熟库。
先说结论:
- 安装一行:
npx skills add https://github.com/cloudflare/security-audit-skill --skill security-audit,我们实测不到 3 秒装进~/.claude/skills/security-audit。 - 在项目目录里说「security audit this codebase」就会触发。README 写明它分六个阶段:侦察、覆盖驱动的猎取、候选验证、结构化输出、独立复核记录、出报告。
- 故意留漏洞的 Node 练习项目 DVNA,standard 档跑了 52 分 57 秒,用了 49 个 Agent:14 个候选,4 条 confirmed(3 条 high、1 条 medium),10 条 needs_validation,0 条 rejected。
- 命令注入、SQL 注入这类「一眼就是漏洞」的,在只读源码、不运行代码的条件下被归为 needs_validation,而不是直接定成 confirmed。这是 skill 的规则:结论取决于运行时行为的,必须实际跑过才算确认。
- 成熟库 serve-static + send:quick 档约 4 分钟,standard 档 6 个猎手审完 12 个覆盖单元,都是 0 发现。它不会为了交差硬凑问题。
security-audit skill 是什么
据 Cloudflare 博客,这个 skill 最初大约 450 行,跑在单个仓库上,团队反复调提示词直到能挖出真实漏洞,之后才在外面加编排,变成后来的漏洞挖掘系统。开源出来的是整理过的版本,README 的原话是:
This is the skill that seeded Cloudflare's vulnerability discovery harness … this skill is the single-repo starting point it evolved from.
它适合的场景:
- 自己的项目上线前,想让 AI 做一遍有记录、可复查的安全审计;
- 想知道 AI 审计到底看了哪些地方,而不是只拿到一句「没发现问题」;
- 想参照 Cloudflare 的做法搭自己的审计流程。
六个阶段(README 原文)
README 写的是「The skill runs a structured audit in six phases」:
| 阶段 | README 原文 | 做什么 |
|---|---|---|
| 1 | Reconnaissance | 摸清架构、信任边界和输入面,写出 architecture.md 和覆盖账本 coverage-ledger.json |
| 2 | Coverage-led hunting | 按账本里的单元分派隔离的猎手 Agent,再用覆盖评审 Agent(coverage critic)找没覆盖到的地方 |
| 3 | Candidate validation | 每个候选交给一个新的验证 Agent,让它尝试推翻 |
| 4 | Structured output | 把 confirmed、needs_validation、rejected 写进 findings.json,用自带脚本按 schema 校验 |
| 5 | Independent record verification | 再换新的 Agent 核对每条最终记录引用的源码 |
| 6 | Target-neutral reporting | 生成 REPORT.md、FINDINGS-DETAIL.md、NEEDS-VALIDATION.md |
旁注:Cloudflare 博客里写的是「The skill was written to run a 7-phase audit in one session」,多出的一步是把复核后的结果提交到他们内部的 ingest API。开源版 README 按六个阶段写,本文以 README 为准。
三种结论的区别,README 原文是:
confirmedhas a complete source trace and bounded observed result,needs_validationhas an exact unresolved fact and no severity, andrejectedrecords a disproved candidate.
也就是说:confirmed 要有完整的源码链路和观察到的结果;needs_validation 要写清楚卡在哪个具体事实上,而且不给严重度;rejected 是被推翻的候选,留着是为了下次别再报。
三档 profile:quick、standard、deep
SKILL.md 里的原文(默认 standard):
| 档位 | SKILL.md 原文(节选) | 适合 |
|---|---|---|
| quick | a bounded pass for small targets, re-runs, or a fast first look … run exactly one hunter wave followed by exactly one final coverage-critic pass | 小仓库、先粗看一遍 |
| standard | the workflow as written | 大多数项目 |
| deep | for high-stakes or large targets … run critic waves to a clean pass | 大型或高风险项目 |
SKILL.md 还规定 quick 或只审部分目录的运行「must present itself as partial coverage」,报告里要写明只覆盖了一部分。
第一步:Claude Code 指向本站
在全局 ~/.claude/settings.json 里写:
{
"env": {
"ANTHROPIC_BASE_URL": "https://api.aicodewith.ai",
"ANTHROPIC_AUTH_TOKEN": "你的 API Key"
},
"model": "claude-sonnet-5-5"
}
ANTHROPIC_BASE_URL 不要带 /v1。我们实测用的是 Claude Code 2.1.310 + claude-sonnet-5-5。更多配置方式见 Claude Code 接入第三方 API 教程。
这个 skill 会并行起很多子 Agent,一次 standard 审计动辄几十个 Agent,比普通对话吃额度。子 Agent 用什么模型可以参考 Claude Code 配置 Haiku 5.5 子 Agent。
第二步:安装 skill
# 装到用户级(所有项目可用)
npx skills add https://github.com/cloudflare/security-audit-skill \
--skill security-audit \
--global
# 只装给 Claude Code,跳过交互确认
npx skills add https://github.com/cloudflare/security-audit-skill \
--skill security-audit --global --agent claude-code -y
我们用第二条,不到 3 秒装好,终端提示 ✓ security-audit (copied) → ~/.claude/skills/security-audit。README 写的要求有三条:支持工具调用和并行子 Agent 的模型、Node.js(跑两个零依赖的校验脚本)、以及一个系统级沙箱,用来运行目标代码。
沙箱这条要特别注意,README 原文:
Without these controls, the workflow keeps the lead as needs_validation instead of executing target code.没有隔离环境,它就不执行目标代码,相关的候选只能停在 needs_validation。我们这次两个目标都只做源码静态审计,没装依赖、没启动服务、没联网,所以结果里 needs_validation 偏多,下面会看到。
Hallmark 这类 skill 的装法也一样,可以对照 Hallmark v2 网页设计 skill 实测。
实测一:故意留漏洞的 DVNA,standard 档
选目标
我们选了 appsecco/dvna(Damn Vulnerable NodeJS Application),一个专门用来演示 OWASP Top 10 的 Express 练习项目,MIT 许可,核心代码只有几百行。克隆到本地后,我们先删掉仓库自带的 docs/ 漏洞讲解,免得 Agent 直接照着答案抄,然后在本地提交一次,保证工作区干净。
整个过程只在本机读源码,不运行这个应用,也不碰任何线上服务。
启动
在仓库目录里启动 Claude Code,输入:
对这个仓库做一次安全审计(full audit),profile 用 standard,按 skill 写的完整流程走(侦察、猎手、覆盖评审、候选逐条交给新的验证 Agent 反驳、结构化输出、记录复核、报告),输出到 /workspace/sa/audit/dvna-run-1。只做源码静态审计:不要运行、构建或测试目标代码,不要安装依赖,不要启动服务,不要访问任何网络服务。Bash 可以用来读文件和运行 skill 自带的 node 校验脚本。
权限模式踩了一个坑:我们一开始用 auto mode,Claude Code 读了一会儿这个「漏洞大全」仓库后,把写文件和 Bash 全拦了,报错原文是:
Error: Auto mode could not evaluate this action and is blocking it for safety — a safety check separate from auto mode blocked this request because of earlier conversation content
提示里也说了,这种情况换掉 auto mode 或者新开会话。我们改成 --permission-mode dontAsk,用 --allowedTools 放行 Read、Write、Bash、Agent 等,同时用 --disallowedTools 禁掉 npm、npx、curl、wget、docker 和联网工具。注意 Bash 要整体放行:只放行 Bash(node:*) 这类前缀时,带 && 的复合命令会被拒,模型干脆不起子 Agent,只交了一份没走流程的报告。
侦察和覆盖:评审 Agent 一轮轮找漏看的地方
先是 4 个侦察 Agent 写 architecture.md 和覆盖账本,然后进入猎取。这一段最能看出它和「让 AI 看一遍代码」的区别:
- 第 1 波 4 个猎手(注入与文件、渲染与越权、认证与会话、配置与供应链),猎手 A 交回 6 个候选;
- 每波之后由一个没参与猎取的覆盖评审 Agent 检查账本,第 2 波后的评审补了 3 个单元:上传解析、可用性和错误路径、flash 与 session 日志;
- 「最终干净」评审发现部署单元列了
wait-for-it.sh却没人看过,又派第 4 波猎手重审; - 直到连续两个评审通读全部 25 个单元都没再提缺口,才宣布覆盖结束。
最后账本是 25 个单元:12 个 covered、12 个 candidate、1 个 blocked。blocked 的是 express-fileupload 的多部分解析,因为仓库里没有 node_modules 和 lockfile,看不到它怎么把字段并进 req.body。
下面是这一段的终端输出(Claude Code 原文,节选):

security-audit-skill-coverage-critic-terminal
逐条让新 Agent 反驳
覆盖结束后,14 个候选各交给一个新的验证 Agent。给验证 Agent 的提示词里写着「You did not write this candidate. Try to refute it from repository source」,并规定:决定性的事实如果是第三方库、浏览器或部署环境的行为,源码看不出来,结论就是 needs_validation,并写明卡在哪;只有源码能推翻时才判 rejected。

security-audit-skill-verifier-terminal
几个值得注意的地方:
- 验证 01 把 mathjs 计算耗时那条留在 needs_validation,理由是「mathjs 3.10.1 表达式的开销是库的行为,源码看不出来」;验证 02 对 XXE 也一样,
noent:true会不会真去解析外部实体要看装的 libxmljs 版本。 - 验证 07 把
/app/useredit按请求体里的 id 改任意用户这条定为 confirmed,因为整条链路都在本仓库代码里。 - 验证 13 自己写错了一个路由:它的记录里出现了一个不存在的
POST /app/admin/useredit。主会话去源码里搜了一遍,确认真实路由是POST /app/useredit(routes/app.js:58),在最终记录里改了过来。
之后第 5 阶段再起 14 个新 Agent 核对每条最终记录,13 条原样通过,1 条(adminusers 页面的 innerHTML)被换成范围更窄的阻塞项。两个校验脚本(validate-findings.cjs、validate-coverage-ledger.cjs)都通过。
结果

security-audit-skill-dvna-result-terminal
4 条 confirmed:
| 严重度 | 问题 | 位置 |
|---|---|---|
| high | 重置密码的令牌就是 md5(login),知道用户名就能重置任意账号的密码 | core/authHandler.js:78、routes/main.js:65 |
| high | POST /app/useredit 按请求体里的 id 改用户,也不校验当前密码,任何登录用户都能接管别人的账号 | core/appHandler.js:145 |
| high | /app/admin/usersapi 只校验登录不校验角色,返回所有用户的 bcrypt 哈希和角色 | routes/app.js:42 |
| medium | products.ejs 用 <%- %> 原样输出商品字段和搜索词,存在存储型和反射型 XSS | views/app/products.ejs:20,49-53 |
10 条 needs_validation:SQL 注入(usersearch)、命令注入(ping)、mathjs eval 执行代码、node-serialize 反序列化、XXE、开放重定向、/app/useredit 缺 CSRF 防护、adminusers 页面 innerHTML XSS、匿名 session 占内存、mathjs 计算占满事件循环。
NEEDS-VALIDATION.md 里每条都写了卡住的具体事实,和一个在隔离环境里用假数据验证的步骤。比如命令注入那条,卡的是「exec 的 shell 行为没有实际观察到,进程权限未知」,给的本地步骤是在断网沙箱里提交 127.0.0.1; id。
所以要把这些推到 confirmed,就得按 README 的要求准备一个断网、限资源的沙箱,让它真正执行。只读源码的条件下,它宁可少下结论。
实测二:成熟库 serve-static + send,0 发现
对照组我们选了 Express 官方的静态文件中间件 serve-static,以及它底层负责路径限制的 send 1.2.1,都是 MIT 许可、用了很多年的库。同样只读源码。
第一轮 quick 档(只审 serve-static 仓库):约 4 分钟,单人审完,0 条 confirmed、0 条 needs_validation。3 个覆盖单元里 2 个 covered,1 个 blocked,因为路径穿越、隐藏文件、符号链接这些检查都在 send 里,而 send 不在这个仓库。报告老老实实写了「This is partial coverage」。
第二轮 standard 档(把 send 1.2.1 源码也纳入范围):4 个侦察 Agent + 6 个猎手 Agent,审完 12 个覆盖单元,包括 URL 路径到文件路径的转换、Range 和条件请求、重定向和错误页渲染、响应头和 MIME,全部 covered,0 个候选。猎手还列出了几个看不到的点(fresh、range-parser、parseurl 的行为,这几个依赖不在磁盘上),交给覆盖评审判断。

security-audit-skill-serve-static-terminal
这一轮跑到 18 分 31 秒时,覆盖评审 Agent 因为测试账号额度用完、接口返回 402 而中断,没有走到最终报告。所以这一节只说到猎取阶段:两轮下来,成熟库在源码层面审不出问题。
这是正常结果。serve-static 和 send 被大量项目用了多年,路径限制这类问题早就被反复审过。审不出东西,至少说明它不会为了交差乱报;配合上面 DVNA 的 14 个候选看,它对真有问题的代码也报得出来。
实测耗时汇总
| 目标 | 档位 | 耗时 | Agent 数 | 结果 |
|---|---|---|---|---|
| 安装 skill | npx skills add | 不到 3 秒 | — | 装到 ~/.claude/skills/security-audit |
| DVNA | standard | 52 分 57 秒 | 49(侦察 4、猎手 11、覆盖评审 6、候选验证 14、记录复核 14) | 14 候选:4 confirmed、10 needs_validation、0 rejected |
| serve-static | quick | 约 4 分钟 | 单人 | 0 confirmed、0 needs_validation |
| serve-static + send | standard | 18 分 31 秒时中断 | 侦察 4、猎手 6 | 12 单元全部 covered,0 候选 |
全部用 Claude Code 2.1.310 + claude-sonnet-5-5。
使用建议
- 先用 quick 摸底,再跑 standard:quick 只跑一波猎手和一次评审,几分钟出结果;standard 会一轮轮补覆盖,我们这两个小项目都跑了 18 分钟以上。
- 想要 confirmed 就准备沙箱:没有断网、限资源的隔离环境,涉及运行时行为的候选只会停在 needs_validation。不要在自己的开发机上直接放开执行。
- 权限模式别用 auto mode 审漏洞仓库:我们实测 auto mode 会因为对话内容把写文件和 Bash 全拦掉。用
dontAsk加白名单,同时禁掉安装和联网命令。 - 多跑几次:README 原文「In our test runs, a single run found roughly half of the vulnerabilities that repeated runs found in total」,单次只能找到多次累计的一半左右。同一个输出目录再跑,它会读上次的账本,专补缺口。
- 人工抽查行号:这次主会话自己发现了验证 Agent 写错的路由。复核 Agent 通过得很快,报告里引用的关键行号最好自己再对一遍。
想让长任务在后台跑、跑的过程中停下来问你,可以看 Claude Code MCP Tasks 和 Elicitations。
常见问题
security-audit skill 是什么?
Cloudflare 开源的代码安全审计 skill,给 Claude Code 这类能并行起子 Agent 的编程客户端用。它把审计拆成六个阶段,由互相隔离的 Agent 分别侦察、找漏洞、查覆盖缺口、反驳候选、复核记录,最后出报告,MIT 许可。
怎么安装和触发?
npx skills add https://github.com/cloudflare/security-audit-skill --skill security-audit,加 --global 装到用户级。在项目目录里说「security audit this codebase」或「find security vulnerabilities in ./src」就会触发。不指定输出目录时,默认写到 ~/security-audit-skill/<仓库名>/run-<N>。
是六个阶段还是七个阶段?
开源版 README 写的是六个阶段。Cloudflare 博客里说的「7-phase audit」多了一步:把复核后的结果提交到他们内部的 ingest API。
confirmed、needs_validation、rejected 有什么区别?
confirmed 有完整的源码链路和观察到的结果,带严重度;needs_validation 写明卡在哪个具体事实上,不给严重度;rejected 是被验证 Agent 推翻的候选,保留下来避免下次重复报。
为什么命令注入只算 needs_validation?
我们这次禁止运行目标代码。exec 的实际 shell 行为和进程权限没有观察到,按 skill 的规则只能停在 needs_validation,并附上在隔离环境里验证的步骤。准备好沙箱再跑,才可能推到 confirmed。
审了一个库是 0 发现,正常吗?
正常。我们审 serve-static + send 两轮都是 0 发现。成熟库被反复审过,审不出东西说明它不会硬凑问题。报告会写清楚覆盖了哪些单元、哪些看不到,可以据此判断这次审到了什么程度。
用第三方 API Key 能跑吗?
能。我们把 Claude Code 的 ANTHROPIC_BASE_URL 指向 https://api.aicodewith.ai,侦察、多波猎手、覆盖评审、候选验证、记录复核和出报告都跑通了。一次 standard 审计会起几十个子 Agent,额度消耗比普通对话大得多。Codex、Cursor 也能通过 npx skills 装 skill,配置 Codex 的自定义 provider 可以看 Codex config.toml 配置指南。



