Security Audit:安全审计官(1)
如果你让一个编码 agent “帮我看看这个仓库有没有安全问题”,十有八九会得到一份看起来很专业的清单:这里可能有注入,那里可能有越权,末尾还附上一句"建议进一步审查"。读完你会有点恍惚:这些是真的漏洞,还是模型顺口编出来的?没有人能回答,因为写清单的那个 agent,同时也是给自己打分的那个。
Cloudflare 开源的 security-audit-skill 想解决的就是这件事。它不是又一个扫描器,而是一个"技能包"(skill):装进你的编码 agent 之后,agent 会把自己拆成一支分工明确、互相隔离的小队,按固定流程做一次完整的安全审计,并且保证"找洞的人"和"验洞的人"绝不是同一个。本周它在热度榜上排在第一位:周报快照里总 star 为 46,962,本周新增 15,072;仓库页面当前显示 50.3k。
这是系列的第 1 部分,我们先弄清楚它是什么、核心设计有哪些讲究;第 2 部分再聊怎么装、怎么用、有什么限制。
一、它是什么:把 agent 变成安全审计员
README 开篇的一句话很准确:这是一个编码 agent 的 skill,让你的 agent 变成安全审计员。它编排一组相互隔离的 agent,依次走完侦察、覆盖率驱动的挖掘、候选验证、结构化输出、独立记录核验,以及与目标无关的报告。GitHub 上的简介说得更短:多阶段安全审计,结果经过独立验证,并且是机器可读的。
注意"skill"这个词。它不是一个要你部署的服务,也不是一个要你配置的 CLI,而是一堆写给 agent 看的 Markdown 说明书,外加几个小脚本。agent 读了这些说明书,就知道该先做什么、再做什么、什么情况下不能下结论。你可以把它想象成一本"审计员入职手册":手册本身不会挖洞,但照着手册干活的人,行为会规矩得多。
它还有一段颇有意思的来历。README 说,这个 skill 是 Cloudflare 漏洞发现 harness 的种子,对应的文章是《Build your own vulnerability harness》。那套 harness 后来长成了多阶段、覆盖全公司的系统;而眼前这个 skill,是它演化出来的"单仓库起点"。换句话说,你拿到的不是一个玩具版,而是大系统的原型:针对一个仓库,它保留了最核心的骨架。
二、六阶段流水线:从摸清地形到交付报告
整个审计分六个阶段。我按 README 的描述,用大白话走一遍。
第 1 阶段:侦察。 像一个新来的审计员先看地图。agent 要梳理架构、信任边界、输入面、已有的证据,以及"确定性的覆盖",并把结果写进两个文件:architecture.md 和 coverage-ledger.json。后者是"覆盖账本",后面所有阶段都围着它转。
第 2 阶段:覆盖率驱动的挖掘。 这里没有让一个 agent 漫无目的地翻代码,而是从账本里拿出一个个"单元",分派给相互隔离的 hunter(猎手)。每个猎手检查完要把做过的检查记下来。另外还有一种角色叫 coverage critic(覆盖评审),专门负责从账本里找缺口:哪些单元没人看、看得不够。
第 3 阶段:候选验证。 猎手交上来的只是"候选",不是结论。每个唯一的候选,都会交给一个全新的 verifier(验证者),任务只有一个:试图推翻它。
第 4 阶段:结构化输出。 候选被分成 confirmed、needs_validation、rejected 三类记录,写进 findings.json,并且要按 report-schema.json 校验格式。
第 5 阶段:独立记录核验。 又一批全新的 agent 出场,核验最终记录里的源码论断。如果某条记录被实质性替换了,还要再找另一个独立的 verifier 看一遍。
第 6 阶段:与目标无关的报告。 最后从已核验的记录和覆盖账本里,派生出三份文档:REPORT.md、FINDINGS-DETAIL.md 和 NEEDS-VALIDATION.md。
读完你会发现一个规律:几乎每个阶段都在回答同一个问题——“这个结论凭什么可信?“侦察阶段留下账本,是为了让后面的覆盖可以被检查;验证阶段换新 agent,是为了让结论可以被反驳;核验阶段再换一批,是为了让最终的措辞可以被复查。流水线的重点不在"挖得多”,而在"每一步都留下可以被下一步质疑的东西”。
脚本在哪里出场
有意思的是,流程里并不全靠模型"自觉"。README 明确写了两处由父 agent 运行的校验脚本:
- 创建覆盖账本之后,以及之后每次更新账本,父 agent 都会运行 validate-coverage-ledger.cjs;
- 在第 4 阶段,以及第 5 阶段每次替换记录之后,父 agent 都会运行 validate-findings.cjs。
这两个校验器都是"零依赖"的 Node.js 脚本,仓库里还各配了测试文件(validate-findings.test.cjs 和 validate-coverage-ledger.test.cjs)。这个设计很朴素,也很聪明:模型负责需要判断力的部分,格式、结构、一致性这类可以机械检查的东西,就交给确定性的代码。agent 再怎么"灵光一现",写出来的记录也过不了 schema 这一关。
三、对抗式验证:找洞的和验洞的不是同一个
如果只让我挑这个项目最值得学的一个点,我会选这里。
README 的"设计原则"里有一条直白得近乎冷酷:对抗式验证——检查发现的那个 agent,绝不是发现它的那个 agent。
为什么要这样?想想你自己写的代码。写完之后自己 review,是不是特别容易"看不出毛病"?因为你脑子里装着自己的意图,读到每一行都自动补全了上下文。让一个模型验证自己刚刚提出的漏洞假设,也是同样的毛病:它已经"相信"了这个结论,再去核对时,倾向于找支持的证据,而不是找反例。
这个 skill 的做法是物理隔离:
- 猎手只负责提出候选,不负责判决;
- 候选交给一个全新的 verifier,并且 README 说它的任务是"尝试推翻",不是"帮忙确认";
- 到了最终记录,还有新的 agent 去核验源码论断;
- 实质性替换过的记录,再追加一个独立 verifier。
"全新"这个词在 README 里反复出现:fresh verifier、fresh agents。它的意思是没有上下文污染——验证者没见过猎手的推理过程,只能从源码本身判断。这很像学术界的盲审,也像公司里"开发不能给自己的代码点批准"的规矩。
严重度要看影响,不看"偏离清单"
另外两条设计原则同样值得说,因为它们专治安全报告里最常见的"噪音":
- 严重度要有影响支撑。 README 的说法是"可能性乘以影响",而不是"偏离了某个检查清单"。清单上没打勾,不等于有风险。
- 纵深防御的缺口不是漏洞。 如果 A 层已经挡住了攻击,那么缺少 B 层只算一条加固建议。
这两条的价值在于,它们逼着 agent 把"理论上不够完美"和"真的能被打穿"分开。任何做过安全评审的人都知道,一份塞满"中危"的报告,往往比一份只有三条真问题的报告更没用,因为读的人会直接关掉。
四、三种裁决与机器可读的结果
第 4 阶段的三种记录,是整个流程里最有"工程味"的设计。README 对它们的定义非常克制:
- confirmed(已确认):有完整的源码追踪,并且有"有界的观察结果";
- needs_validation(待验证):有一个明确的、尚未解决的事实,并且不给严重程度;
- rejected(已否决):记录一个被证伪的候选。
我特别喜欢中间那一类。很多工具遇到"八成有问题但没法当场证明"的线索时,要么硬报成漏洞,要么悄悄丢掉。这里的做法是:留着,但要写清楚"到底差哪一个事实没确认",并且不许标严重度。这样一来,读报告的人一眼就知道哪些是板上钉钉的,哪些还需要有人去补一块拼图。
对应的原则写在设计原则的第一条:只确认已经成立的边界失效;如果线索有源码依据但被卡住了,就留作 needs_validation,并写明确切的未解决事实。
被否决的记录也有价值。它们说明"这条路有人走过,走不通",下一次运行就不用重复论证。
为什么强调"机器可读"
三类记录都落在 findings.json,并受 report-schema.json 约束(README 说这是覆盖三种裁决的 JSON schema)。人类可读的 REPORT.md 等文档,则是从这些已核验的记录派生出来的,而不是反过来。
这个顺序很关键:先有结构化数据,再生成文档。好处是数据可以被脚本校验、被其他工具消费,文档不会和数据脱节。如果你想把审计结果接入自己的流程,比如导入工单系统,机器可读的 JSON 比一份散文式的报告好用得多(具体怎么接入,README 未说明,这里只谈设计思路)。
五、文件地图:一套提示词拼成的方法论
仓库的主体在 skills/security-audit 目录下。把 README 里的文件表读一遍,你会发现它其实是一座"提示词图书馆"。
主干文件:
- SKILL.md:设置、核心原则、平台术语、流程概览和审计反模式,是 agent 的总入口;
- RECONNAISSANCE.md:第 1 阶段的侦察提示和综合说明;
- HUNTING.md:第 2 阶段的编排、挖掘方法和验证规则;
- VALIDATION-AND-REPORTING.md:第 3 到第 6 阶段的候选验证、结构化输出、记录核验和报告。
攻击类文件: 这是我觉得最有想法的一部分。ATTACK-CLASSES.md 放的是核心、wildcard 和"明显的东西"(obvious-things)三类攻击提示;然后按目标类型,分出了一整排专门文件:
- 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、序列化、队列、代理、webhook、流协议;
- RESOURCE-EXHAUSTION-AND-AVAILABILITY.md:共享资源、配额、队列、worker、运营者开销;
- DATA-ISOLATION-AND-LIFECYCLE.md:租户隔离、缓存、搜索、导出、备份、迁移、删除、恢复;
- DESKTOP-MOBILE-AND-LOCAL-IPC.md:原生应用、深度链接、webview、导出组件、辅助进程、守护进程、本地 IPC。
这样分的好处不难理解:审计一个 C 语言网络库和审计一个 SaaS 后台,关注点天差地别。把攻击思路按目标类型拆成独立文件,agent 可以只加载与目标相关的那几份,而不是把所有知识一股脑塞进上下文。至于具体怎么选择加载哪些文件,README 没有展开,我们只能从文件命名里读出这种"按目标类型分册"的意图。
机器检查的部分: report-schema.json、两个校验脚本和它们的测试文件,构成了前面说的"确定性护栏"。
覆盖账本:让"看过了"可以被检验
最后回到那份 coverage-ledger.json。它是整个流程的骨架:侦察阶段建立它,挖掘阶段按它派活,评审角色拿它找缺口,报告阶段再把它和已核验的记录一起用来派生文档。
README 还提到一点:多次运行是累加的。同一个仓库审第二遍时,skill 会利用上一次的账本和发现,瞄准没覆盖到的缺口,重新验证已经变化的源码,并带入仍然有效的当前源码证据,同时不会把"过期的"或"还没解决的"工作当成已经覆盖。
这一点很实在。代码天天在变,上周的"已审查"到这周可能早就不成立。账本让"覆盖"变成一个可以过期、可以重新验证的状态,而不是一句一劳永逸的"我们审过了"。README 还给了一个数据点:在他们的测试运行中,单次运行大约只能发现多次运行总共发现的漏洞的一半。这是 Cloudflare 自己的测试结论,README 没有给出更详细的实验设置。
本部分小结
把这一部分压缩成几句话:
- security-audit-skill 是一个给编码 agent 用的"审计员入职手册",来自 Cloudflare 漏洞发现 harness 的演化,是那套系统的单仓库起点;
- 流程分六个阶段:侦察、覆盖率驱动挖掘、候选验证、结构化输出、独立记录核验、与目标无关的报告,每一步都留下可被下一步质疑的产物;
- 核心理念是对抗式验证:发现者和验证者永远不是同一个 agent,验证者的任务是推翻,而不是确认;
- 结论分为 confirmed、needs_validation、rejected 三种,并且以 JSON schema 约束的结构化记录为准,报告文档由记录派生;
- 仓库本质是一座按目标类型分册的提示词图书馆,外加零依赖的校验脚本做确定性护栏。
下一部分预告
下一部分我们会聊更接地气的话题:怎么用 Skills CLI 把它装进你的 agent,几句话就能触发的完整审计是什么样,输出目录默认放在哪里;它对运行环境有哪些硬性要求,尤其是那套关于沙盒的规定;多次运行为什么值得;以及它适合谁、不适合谁、有哪些局限。
- 点赞
- 收藏
- 关注作者
评论(0)