用 Claude 给自己的代码找漏洞再修好:官方六步法和参考仓库的 skill 实测,再加三轮“缩小攻击面”检查
整理官方讲用 Claude 找漏洞、修漏洞的两篇文章,在一个埋了 6 处问题的小服务上实测:参考仓库的 skill 从威胁模型一路跑到补丁,再照研究文章做三轮缩小攻击面的检查。6 处都找到了,另有几个我们没埋的真问题;4 个补丁都过了测试,但有一条回归测试失败的原因不对。直连官方约 13 美元。
本站已实测,测试日期:2026-10-21。
本页目录(13 节)
这篇教程解决一个问题:想用 Claude 把自己的代码从头查一遍,找出漏洞,再修好,而且修完能证明真修好了。
本站之前写过三样现成的工具,它们和这篇的关系是这样的:
| 什么时候 | 用什么 | 本站文章 |
|---|---|---|
| Claude 写代码的当下 | security-guidance 插件,写完就查、当场改 | 安全检查插件 |
| 想查的时候,开箱即用 | Claude Security 插件,几个代理扫全库,评审投票,再出补丁 | Claude Security 插件实测 |
| 想自己搭一条流程,按自己的威胁模型和标准来 | 官方的方法文和配套的参考仓库(这一篇) | — |
现成的插件把流程封装好了,好处是省事,坏处是每一步怎么判断你插不上手。这一篇讲的是官方在 2026 年 5 月和 10 月发的两篇文章:一篇讲和多家安全团队一起找漏洞、修漏洞总结出的六步法,配了一个开源的参考仓库,里面有每一步的 skill;另一篇是 Anthropic 前沿红队的研究,讲换个角度,不去一个个找漏洞,而是把用不着的代码挪到登录或开关后面,让还没被发现的漏洞也碰不到。
本站在服务器上把这两条路都跑了一遍,对象是一个专门写的小服务,埋了 6 处问题。
| 步骤 | 结果 |
|---|---|
| 威胁模型 | 14 条威胁;顺手点出了 6 处里的 5 处,另有 2 个我们没埋、核实属实的问题 |
| 扫描 | 报了 8 条:3 处经典漏洞和“上传先落盘”都中了,另外两处各说中一半;我们放的几处干扰没有误报 |
| 分级 | 确认 7 条、去掉 1 条。不给威胁模型时,3 个高危被降成中危;给了之后升回来 |
| 修补 | 前 4 条的补丁都能用,打上后 28 个测试全过;有一条回归测试失败的原因不对 |
| 缩小攻击面三轮 | 两处攻击面问题加门,测试逐条不变;那条配置组合的问题也找到了,另有几处是我们没埋的 |
| 花费 | 直连官方约 13.3 美元,交互式那一次走酷站AI 约 4.3 美元,都是 Claude Code 按官方价的估算 |
下面细说。
一、官方的思路:找到不难,难的是后面三步
Claude 官方 2026 年 5 月的文章《Using LLMs to secure source code》是 Anthropic 和多家安全团队合作找漏洞之后的总结。核心判断只有一句:让模型找漏洞,现在很容易并行铺开;瓶颈挪到了验证、分级和修补。 文章给的数字是,截至 5 月 22 日,他们在开源软件里报出了 1,596 个漏洞,据他们所知修好的是 97 个。
文章把做法排成六步,前两步每个代码库做一次,后四步反复循环:
| 步骤 | 做什么 | 为什么 |
|---|---|---|
| 1. 威胁模型 | 扫之前先写清楚:系统是什么、哪些输入可信、哪些是攻击面、哪类问题算漏洞 | 误报最常见的原因是模型搞错了信任边界,把可信的配置文件当成攻击者能改的,或者把公网服务当成内网的 |
| 2. 沙箱 | 搭一个隔离的环境,智能体在里面跑,必要时在里面验证 | 一是防止智能体越界,二是能编译、跑测试、复现,不能利用的发现会少很多 |
| 3. 发现 | 给足上下文,提示词反而要短;先把代码分成几块,再分头找 | 检查清单写得越细,模型越放不开,找到的新问题越少 |
| 4. 验证 | 另起一个不看发现过程的智能体,默认每条都是误报,去找它错的理由 | 发现追求不漏,验证追求不错。同一个智能体两件事一起做,会把真问题自己筛掉 |
| 5. 分级 | 按根因去重,按能否触达、需要什么前提、要不要登录、影响多大来定级 | 一堆重复、定级虚高的报告送到开发手里,他们会连真问题也不看了 |
| 6. 修补 | 先写一个现在会失败的测试,再修,再跑全部测试,最后让新的智能体换个角度攻一次 | 模型容易只修报出来的那一处,不修根因;也容易修得太死,把正常功能挡掉 |
几条值得记住的经验,都出自这篇文章里各团队的反馈:
- 威胁模型写清楚的系统,模型的发现九成可以利用。 反过来,一个团队 40% 的“误报”其实都能复现,只是不符合那个项目的威胁模型,被开发团队驳回了。
- 翻历史修复是捷径。 有团队把几百个过去的漏洞修复整理成“问题长什么样”的提示,问模型两个问题:当年修全了没有,别处有没有一样的写法。一小时找到三个可以利用的问题。
- 加一个唱反调的验证者,不能利用的发现大约少一半;再要求验证者把问题复现出来,误报几乎没有了。 但复现不出来不等于是误报。
- 补丁要过四关,从便宜的到贵的: 能构建、新测试通过;原来的复现失效;原有测试全部通过;新开一个智能体再攻一次。
- 补丁最后要有人负责。 模型写的补丁常见的毛病是修症状不修根因、把正常输入也挡掉、切断了别的服务要用的接口。
第二步的沙箱,文章说得很直白:一个团队告诉模型“没有网络”,其实有,模型照样从 GitHub 取了东西;另一个团队的智能体在扫描途中回复了一个 GitHub issue。限制要靠代码和配置强制,不能靠提示词。搭不出像样的沙箱时,可以先只做静态的发现,代价是验证要多花人力。
二、参考仓库里有什么
文章配了一个开源仓库 anthropics/defending-code-reference-harness(Apache 2.0,README 写明不再维护、不接受贡献)。里面分两部分:
| 部分 | 是什么 | 会不会执行目标代码 |
|---|---|---|
| Claude Code 的 skill | /quickstart、/threat-model、/vuln-scan、/triage、/patch、/customize,另有检测和响应用的 /dnr-hunt、/dnr-respond |
前五个只读写文件,不构建、不运行、不联网。README 的说法是,只要在交互式会话里逐个批准工具调用,不用沙箱 |
harness/ 自动流水线 |
侦察、发现、验证、去重、报告、补丁一条龙,默认针对 C/C++ 的内存问题,用 Docker 和 ASAN | 会。必须在 gVisor 容器里跑,出网只放行模型接口,不在沙箱里时拒绝启动 |
本站只实测了前一部分,四个 skill 串起来就是六步里的 1、3、4、5、6。自动流水线要装 Docker 和 gVisor,本站的测试服务器上没有装,这部分按 README 写,没有实测。
几个用之前要知道的细节:
- 它的 README 建议把子代理的模型固定下来,
export CLAUDE_CODE_SUBAGENT_MODEL=<模型>,免得会话用 Opus、分出去的子代理用了别的模型。 - 仓库的
.claude/settings.json只设了一个环境变量ANTHROPIC_CUSTOM_HEADERS,让每个请求带上anthropic-cyber-runbook: skills这个请求头。仓库的文档说这是声明用途的标记,只是结构信息,不含内容,可以去掉。 - skill 是项目级的,放在仓库的
.claude/skills/下。官方的用法是在这个仓库里启动 Claude Code,再把要扫的目录作为参数传进去,比如/vuln-scan ~/code/my-service。
三、本站怎么测的
被扫的是本站专门写的一个小服务 notes:团队记笔记用,注册、登录、写笔记、给笔记挂附件、上传头像、从旧版导出文件批量导入。Python 标准库实现,8 个 Python 文件约 530 行,另有 24 个测试。README 写了部署方式(公网 VPS,直接监听 8080,谁都能注册)和每个配置项。
我们在里面埋了 6 处问题,清单只放在本地,没有放到服务器上,免得被扫描读到:
| 编号 | 类别 | 问题 |
|---|---|---|
| C1 | 经典漏洞 | 搜索把关键词直接拼进 SQL |
| C2 | 经典漏洞 | 下载附件时文件名没检查,能读到附件目录外面的文件。git 历史里修过头像的同一种问题,附件这里没修 |
| C3 | 经典漏洞 | 读单篇笔记只查登录,不查是不是主人 |
| A1 | 攻击面:默认配置 | 旧版导入默认关闭,但请求体先被解析,然后才查开关和登录 |
| A2 | 攻击面:过早可达 | 上传附件先把请求体写进磁盘,再查登录 |
| A3 | 攻击面:配置组合 | 拒绝名单写主机名、又没开反查时,规则永远匹配不上,也没有任何提示 |
另外放了几处看着可疑、其实没问题的写法(排序字段用了 f-string 但来自白名单,上传文件名取了 basename 等),看它会不会误报。
示例应用的 git 历史有四次提交:第一次是整个服务,第二次修了头像的路径问题,第三次删掉误提交的 macOS 元数据文件,第四次是下面第一步生成的威胁模型。
| 实测环境 | |
|---|---|
| 日期 | 2026-10-21 |
| 版本 | Claude Code 2.1.304;参考仓库固定在提交 d3bea6b(2026-08-06) |
| 系统 | Ubuntu 24.04,Python 3.12.3 |
| 模型 | Opus 5.5,effort 用默认值(交互式界面上显示是 medium)。/vuln-scan 打分的子代理跑在了 Sonnet 5.5 上(见第五节),从 /triage 起加了 CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1,子代理都是 Opus 5.5 |
| 连接方式 | 直连官方接口,用 claude -p 跑;第六节交互式重跑分级的那一次走酷站AI 中转站 |
| 权限 | --permission-mode acceptEdits,Bash 只放行每个 skill 自己声明的命令;每次加 --max-budget-usd 封顶 |
skill 是在参考仓库的目录里启动的,示例应用用 --add-dir 加进来:
cd defending-code-reference-harness
export CLAUDE_CODE_SUBAGENT_MODEL=claude-opus-5-5
claude -p "/vuln-scan ../notes-app" \
--add-dir ../notes-app \
--model claude-opus-5-5 \
--permission-mode acceptEdits \
--allowedTools "Bash(rg:*)" "Bash(grep:*)" "Bash(ls:*)" "Bash(wc:*)" "Bash(head:*)" "Bash(file:*)" \
--max-budget-usd 4 \
--output-format json
claude -p 里可以直接写 /skill 名字,Claude Code 会先展开再执行,这一点 headless 文档里写着。几个 skill 默认要和你对话(/triage 开头会问信任边界和打分标准),在 -p 里没法回答,所以 /threat-model 用 bootstrap,/triage 加 --auto。
四、第一步:让它先写威胁模型
cd defending-code-reference-harness
export CLAUDE_CODE_SUBAGENT_MODEL=claude-opus-5-5
claude
> /threat-model bootstrap ../notes-app
本站实际跑的是等价的 claude -p "/threat-model bootstrap ../notes-app",参数和上一节一样。
/threat-model 有三种模式。bootstrap 只看代码、文档和 git 历史,自己推一份;interview 用 Shostack 的四个问题(我们在做什么、会出什么错、打算怎么办、做得好不好)访谈系统的主人;bootstrap-then-interview 先推草稿,再拿草稿去访谈,有人能回答问题时官方推荐这个。本站用的是 bootstrap。
结果是示例应用目录里的一份 THREAT_MODEL.md,约 1.2 万字符,八节:系统背景、资产、入口和信任边界、14 条威胁(每条有攻击者、入口、影响、可能性、已有的防护、证据)、不打算管的、要问主人的问题、出处、修补建议。
几处做得好的:
- 从 git 历史里学到了“同类问题”。 它读到了修头像路径的那次提交,把“通过文件接口的路径穿越”列为第一条威胁,状态写“部分防住”,理由是
safe_join()只用在了头像上。 - 把可信的东西排除掉。 README 说配置文件由管理员写、是可信的,它就把“远程攻击者篡改配置”放进了不打算管的一栏,理由照抄 README。官方文章说误报最常见的原因是搞错信任边界,这一步就是在防这个。
- 列了它答不了的问题。 比如 8080 前面到底有没有 TLS、服务用哪个系统用户跑、
legacy_import现在开没开。这些问题留给interview模式去问主人。
最出乎意料的是它顺手给的“线索”。这个 skill 的本职是写威胁,不是找漏洞,可它在读代码时把我们埋的六处里的五处都点了出来(SQL 注入、附件路径穿越、读笔记不查主人、导入在登录前解析、上传先落盘再查登录),还多给了两条我们没埋的:
- 导入格式里
body-lines写成负数,解析器会死循环。 我们用一条带超时的命令核实了:三行内容,5 秒内没有返回。更要紧的是,导入默认是关着的,可import_v1先解析、再查开关、再查登录,所以默认配置下,没有账号的请求也会走进这段解析代码。这条后面第八节还会说到。 - 删掉笔记后附件留在磁盘上,而 SQLite 会把最大的那个笔记编号再发出去,下一个人新建的笔记可能看到上一个人的附件。
它在结尾写明这些只是读代码时看到的,没有运行验证,下一步交给 /vuln-scan。
官方建议把 THREAT_MODEL.md 提交进仓库,代码改了就跟着改;扫描和分级两步都会读它。本站照做了。
五、第二步:扫描
/vuln-scan ../notes-app
/vuln-scan 读目标目录里的 THREAT_MODEL.md,按里面的入口划分重点,每个重点派一个子代理去读代码。源文件少于 15 个时不分头,一个代理从头读到尾,示例应用就是这样。读完后再给每条发现派一个子代理,单独重读那段代码、打一个 1 到 10 的置信分。这一步只排序,不丢掉任何一条,去掉误报是下一步的事。它不构建、不运行,Bash 只能用 rg、grep、ls 这几个只读命令。
跑下来 22 轮,约 3 分钟,1.05 美元,报了 8 条:
| 编号 | 扫描定的级别 | 置信 | 是什么 | 对应 |
|---|---|---|---|---|
| F-001 | 高 | 1.0 | 读单篇笔记不查主人,登录的用户能读到别人的私人笔记 | C3 |
| F-002 | 高 | 0.9 | 搜索把关键词拼进 SQL,能读到别人的数据 | C1 |
| F-003 | 高 | 0.9 | 附件下载的文件名没检查,能读到附件目录以外的文件 | C2 |
| F-004 | 中 | 0.9 | 格式不对的导入内容让解析器死循环,而且解析发生在查登录和查开关之前 | 我们没埋的;和 A1 是同一处 |
| F-005 | 中 | 0.7 | 删掉的笔记的附件被新笔记继承 | 我们没埋的 |
| F-006 | 中 | 0.7 | 上传先落盘再查登录,没登录的请求也会在磁盘上留下文件 | A2 |
| F-007 | 低 | 0.7 | 开了反向 DNS 后,按 IP 写的拒绝规则不再生效 | A3 的一半 |
| F-008 | 低 | 0.6 | 登录的响应时间和注册的“名字已占用”能看出用户名存不存在 | 不算问题,见下一步 |
我们埋的 6 处,C1、C2、C3、A2 都中了,A3 只说中一半:它看到了“开反查后 IP 规则失效”,没看到另一半,也就是默认不开反查时,主机名规则拿 IP 去比,永远不生效。A1 没有作为“攻击面”单独报,但 F-004 把导入在登录和开关之前解析写进了理由。我们故意放的几处看着可疑的写法,一处也没有误报。要说明的是,扫描读到了上一步威胁模型里的那些线索,这些命中不全是这一步自己的功劳。
有个和模型有关的细节。我们按 README 设了 CLAUDE_CODE_SUBAGENT_MODEL=claude-opus-5-5,可是打分的 8 个子代理跑在 Sonnet 5.5 上:会话记录里,主会话每次派活都自己写了 model: sonnet。Claude Code 文档写着,这个变量只是默认值,派活时指定的模型优先;要所有子代理都用同一个模型,还得再设 CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1(2.1.257 起)。下一步我们加上了。
六、第三步:验证和分级
/triage ../notes-app/VULN-FINDINGS.json --auto --repo ../notes-app
/triage 是这套 skill 里最用力的一步:先去重,再给每条发现派 3 个互不相干的验证代理,各自从“这是误报”出发去找反证,最后投票、定级、找负责人。默认开头会问四个问题:系统是什么、最坏的攻击者是谁、用什么打分标准、验证者意见不一时偏向少报还是不漏。--auto 跳过这些,用偏向少报的默认值。
这一步是全程最贵的:60 轮,约 7.5 分钟,5.47 美元,24 个验证代理都用 Opus 5.5。结果:
- 8 条进,7 条确认,1 条去掉,没有重复。 每条的 3 票都一致。
- 去掉的是 F-008。理由是这个服务本来就公开用户名:谁都能注册,头像也是按用户名不登录就能取。登录不限速是另一回事,算加固上的缺口。
- 定级重排了。 死循环从中升到高,理由是不用登录、导入关着也会碰到这段代码。SQL 注入、路径穿越、越权读笔记从高降到中,理由是要登录;新编号复用的那条从中降到低,因为要好几件事刚好凑在一起才会发生。
降级那三条,它在报告最前面专门写了一段提醒:降成中危只是因为打分表把“要登录”算作一个前提,可这里注册是公开的,这一步对攻击者没有成本,这三条影响的是所有用户的数据,应该和死循环一起先修。它还说,交互式地重跑、给出“用户之间的数据泄露”这样的威胁,这三条会升回高。
这正好对上官方文章的一句话:分级的代理要拿到和发现时同一份威胁模型。看了 skill 的说明才知道,--auto 时威胁模型是空的,不会去读目录里的 THREAT_MODEL.md。
它还在报告里写了自己哪里没按 skill 做:排序本该给每条再派一个代理,可 24 个验证者跑完只剩约 2.5 美元预算(我们设的上限是 7),就由主会话自己排了;查负责人要用 git log,那条命令要批准,没跑成。
把威胁模型交给它,再分一次级
为了看清威胁模型的作用,我们在交互式界面里重跑了一次 /triage,不加 --auto,每条只投 1 票省额度。交互式的 Claude Code 用不了赠送的 API 额度,这一次走的是酷站AI 中转站。开头四个问题这样答:
| 问题 | 我们的回答 |
|---|---|
| 系统是什么、不可信的输入从哪来 | 面向公网的 Web 服务,HTTP 请求都不可信 |
| 最坏的攻击者是什么样、什么绝不能发生 | 勾了“用户之间的数据泄露”,再写一句:参照 ../notes-app/THREAT_MODEL.md;绝不能让一个用户读到别人的笔记、附件、会话令牌,或者接管任何账号;注册是公开的,要登录不算门槛 |
| 用什么打分 | 默认的“按前提条件推出高、中、低” |
| 验证者意见不一时 | 偏向少报 |
打分标准和上一次一样,只换了威胁模型。结果:
| 发现 | --auto,每条 3 票 |
交互式,给了威胁模型,每条 1 票 |
|---|---|---|
| 读别人的笔记 | 中 | 高 |
| 搜索的 SQL 注入 | 中 | 高 |
| 附件的路径穿越 | 中 | 高 |
| 导入的死循环 | 高 | 高 |
| 编号复用继承附件 | 低 | 中 |
| 上传先落盘 | 中 | 去掉 |
| 开反查后 IP 规则失效 | 低 | 低 |
| 用户名能探出来 | 去掉 | 去掉 |
前三条正是我们说的“绝不能发生”的事,有了这句话就升回了高危,报告里每条都写明对上了哪一条威胁。
“上传先落盘”这次被唯一的那个验证者判成误报,理由是“只是靠请求量写满磁盘”,属于 skill 规定不报的那一类;上一次 3 个验证者都判它是真的,理由是写进去的文件在攻击停止后还留着。官方文章说单个验证者不够时就多投几票、取多数,这里看得很清楚:一票的结论会摇摆。
这一次 Claude Code 估算 4.25 美元,约 12 分钟。每条只投 1 票,并没有比 3 票那次便宜多少:--auto 那次因为预算不够,跳过了给每条派一个代理来定级的那一步,这次没跳。交互式界面里我们批准了 3 次命令:两次是 cd 进示例应用再跑 git log,一次是验证者用管道串起来的 ls、grep、head。前一种 Claude Code 会单独提醒,理由是先进目标目录再跑 git,可能用上目标仓库里不可信的 hooks 和配置。扫的是别人的代码时,这个提醒值得认真看。
七、第四步:出补丁,再自己过四关
/patch ./TRIAGE.json --repo ../notes-app --top 4
/patch 读分级的结果,按严重程度取前几条,每条派一个写补丁的代理,要求找根因、顺手找同类、改动最小、补丁里带一个回归测试;再派一个评审代理,它只看得到文件、行号、类别和 diff 本身,看不到扫描时写的那些描述。补丁只写成 PATCHES/bug_NN/patch.diff,不会打到你的仓库上。README 说这是故意的:没有 --apply 这个参数,所以也没法被提示注入骗着去用。
为了省额度,我们只修前 4 条。32 轮,约 4 分钟,2.04 美元,4 个补丁都过了评审:
| 补丁 | 修什么 | 怎么修 | 代码改动(不算测试) |
|---|---|---|---|
| bug_00 | 导入的死循环 | body-lines 是负数时报格式错误 |
加 2 行 |
| bug_01 | 搜索的 SQL 注入 | 关键词改成查询参数 | 加 3 行,删 2 行 |
| bug_02 | 附件的路径穿越 | 用仓库里现成的 safe_join,限定在这篇笔记自己的目录;越界返回 400 |
加 5 行,删 2 行 |
| bug_03 | 读别人的笔记 | 改用现成的所有权检查,别人的笔记返回 404 | 加 3 行,删 5 行 |
都是在根因上改,而且都用了仓库里已有的函数,没有另起一套。它自己交代了两件事:bug_03 的 diff 头里行数是它手改的,应用前要检查;bug_00 的测试在修之前会卡死而不是失败,要加超时跑。它还提醒,导入仍然先解析、再查开关和登录,挪到前面是值得做的加固,但不在这个补丁的范围里。
官方文章说补丁要过四关。前三关我们在服务器上用脚本跑,不花额度:
| 关 | 做法 | 结果 |
|---|---|---|
| 能应用、能构建 | 四个 diff 单独、依次 git apply;py_compile |
都能干净地应用,包括手改过行数的 bug_03;编译通过 |
| 原来的问题复现不了 | 把带新测试的测试文件放到没修过的代码上,逐条跑,每条限时 10 秒 | 4 条新测试都失败;死循环那条是超时退出 |
| 没有改坏别的 | 打上补丁后跑全部测试 | 28 个(原来 24 个,加 4 个新的)全部通过 |
| 换个人再攻一次 | 新开一个 claude -p,对补丁后的代码重新 /vuln-scan |
见下 |
第二关有一个要留心的地方。路径穿越那条新测试,在没修过的代码上确实失败了,但失败的原因不对:它返回的是 404,而不是把附件目录外的文件交出来。那篇笔记还没有附件,它自己的附件目录不存在,带上级目录的路径在这一步就解析不下去。我们另跑了一个对照:先给笔记传一个附件,再发同样的请求,没修过的代码就把目录外的文件返回了,打上补丁后返回 400。补丁没问题,是它写的测试只覆盖了“没有附件”这一种情况,修之前那次失败是碰巧。拿到模型写的回归测试,要看它在修之前是不是因为对的原因失败。 这条测试应该改成先上传附件。
第四关我们用 --single --no-score 省掉了分头读和打分:15 轮,约 2 分钟,0.56 美元。结论是没有再找到 SQL 注入、越权和路径穿越,“每条查询都用参数,每个笔记接口都查所有权,读文件都走 safe_join”。它报的 7 条里,4 条是我们没修的那几条(编号复用、开反查后 IP 规则失效、上传先落盘、导入先解析),其余是之前没报过的低危,比如上传时文件名写 .. 会让请求抛异常。这条我们核实了,没修过的代码也一样,不是补丁带进来的。
最后一句官方说得很重:补丁要有人负责。 四个补丁我们逐行看过,都很小,可以直接合;要是补丁大到看不过来,就先让它缩到只改根因,不顺手重构、不改格式。
八、换个角度:不找漏洞,先让用不着的代码碰不到
Anthropic 前沿红队 2026 年 10 月 20 日的研究文章《Using Claude to reduce the attack surface of code》出发点是:一个个找漏洞、修漏洞扩展不了,修好一个严重漏洞,也说明不了剩下的还有多少。换一个做法,把某个部署根本用不着、却暴露在外的代码挪到合适的关口后面:从登录前挪到登录后,从默认打开改成明确打开,从协议的第一条消息挪到会话更深的状态。代码里没被发现的漏洞还在,但更难碰到,严重程度就降了,而且一次降低的是所有未知漏洞的风险。
他们用了三种方法:
| 方法 | 找什么 |
|---|---|
| 默认配置 | 出厂默认配置下就碰得到、但默认功能根本用不着的代码 |
| 配置组合 | 两个选项单看都合理,放在一起会悄悄关掉一个安全控制,或者让行为没有定义而代码仍然可达 |
| 过早可达 | 和安全有关、又比较复杂的代码,在登录这类关口之前、在用户没有任何操作时、或者在协议状态切换之前就会运行 |
他们用 Claude 查了 25 个开源项目,五百多万行代码,C、C++、Java、PHP、Python 都有,找出 493 处。几个结论:
- 11 个需要登录才能用的项目,每一个都有本不该在登录前碰得到的代码。
- 收获最大的是配置组合:162 种和安全有关的组合,程序照单全收,不报错、不警告,文档也没写会怎样;其中 94 种悄悄关掉了一个安全控制。有一种写法在 4 个互不相关的项目里各自出现:管理员用主机名写拒绝规则,而服务器默认不做反向 DNS 查询,拿来比对的只有 IP,主机名规则永远匹配不上,拒绝名单形同虚设。还有一对选项的冲突在一个软件里存在了 21 年。
- 按占代码量的比例算,缩得最多的反而是 OpenSSH。10.4 版的 sshd 里有约 4,500 行新加的、实验性的 ML-DSA-44(一种后量子签名算法)代码,默认没有启用,管理员明确排除了也一样:sshd 先解析客户端发来的密钥,再检查这种密钥类型允不允许。这 4,500 行约占默认 sshd 登录前代码的 16%。他们把问题和补丁报给了 OpenSSH,10.5 版把检查挪到了解析之前;Claude 又指出 10.5 的检查有两处缺口,和维护者几轮来回之后,检查挪进了解析器内部,10.6 版发布。这次修改让未登录的客户端能碰到的代码少了约五分之一。
官方把三种方法做成了一个 skill,正在并进他们的开源扫描服务,还没有公开。文章介绍了它的做法:分三轮,每轮从空的上下文开始,只带上一轮留下的一小段;每一轮都先列入口清单,每条发现都用一次实际运行的跟踪来确认;默认配置的发现要设计一道门,并用项目自己的测试证明默认行为不变,会改变非默认配置的门留给人决定;另外两轮只提修改建议。文章也说,不用这个 skill,照这个方法做也多半有效。
本站就照这个方法写了三段提示词,三轮各开一个新的 claude -p 会话,只把第一轮写的入口清单交给后两轮。在示例应用的一份副本上跑,这份副本退回到了威胁模型提交之前,免得前面的结论影响判断。
第一轮:默认配置
提示词的要点(全文约 570 字):
1. 先列入口清单:网络客户端能发的每一种请求,从哪里进来,默认配置下会依次跑到哪些函数。
写进 ASR/entry-points.md,不超过 40 行,下一轮会把它交给另一个会话。
2. 找出默认配置下碰得到、但默认开着的功能根本用不着的代码。每一条都要实际跑一次来确认:
写个小脚本,通过 notes.app.App.handle 发请求,记录那段代码确实被执行了,不要只凭读代码下结论。
3. 对确认的每一条设计一道门,要求默认配置下程序的行为不变:改之前和改之后各跑一次测试,
结果必须一样。会改变非默认配置下行为的门不要动手,列出来留给人决定。
跑下来 27 轮,不到 3 分钟,0.56 美元。它写了一个探测脚本,用 sys.settrace 记下每个请求实际执行到 notes/ 里的哪几行,再看磁盘上多没多文件、有没有发反向 DNS 查询。确认了两条,都加了门:
| 入口 | 改前 | 改后 |
|---|---|---|
POST /import,未登录,默认配置 |
解析器 37 行里执行了 29 行(合法内容)或 21 行(格式错误) | 执行 0 行 |
POST /notes/<id>/attachments,未登录或不是主人 |
每次在 incoming/ 留下一个文件,最大 10 MB,永不清理 |
不落盘,响应码不变 |
两处的改法都是把检查挪到前面,git diff 一共 6 行。改前改后 24 个测试逐条一致。
它还主动报了一处“和要求不完全一致”的地方:默认配置下,格式错误的导入请求原来返回 400 和解析器的报错,现在返回 404。不跑解析器就没法给出 400,所以这个变化躲不开;它说明了 404 和 README、和现有测试的意图一致,还顺带指出原来的 400 本身就暴露了这个端点存在,然后把要不要回退留给人。这正是研究文章说的那条规矩:门不该改变默认行为,改变了的交给人决定。
三件会改变非默认配置的,它列出来没有动:打开导入后解析仍在登录之前;打开反向 DNS 后,每个请求都要先查一次,按 IP 写的规则也会受影响;server.py 读请求体没有上限。
第二轮和第三轮
后两轮照同样的办法,各开一个新会话,只带上第一轮写的入口清单:第二轮看“过早可达”,第三轮看“配置组合”。提示词要求这两轮只写建议和补丁、不改代码,确认时不联网,用到反向 DNS 的地方一律打桩。两轮分别是 1.28 美元和 1.16 美元。
我们埋的那条配置组合(A3)在第三轮里确认了,而且它注意到仓库自带的示例配置正好就是这个组合。两轮给出的补丁我们没有应用,也没有像第七节那样逐个过四关,所以这里不展开。
三轮合计约 3 美元,比前面扫描加分级的 6.5 美元便宜,看问题的角度也不一样:前面那条路一条条找漏洞,配置组合那一处只说中一半;这三轮不找漏洞,只看哪些代码在不该碰到的时候碰得到,A1、A2、A3 都确认了,另有几处是我们没埋的。反过来,三轮读代码时也看到了 SQL 注入和越权,只是不在它的范围里,列出来没有验证。两条路互相补,不是谁替代谁。
九、花了多少,用了多久
| 步骤 | 连接方式 | 轮数 | 用时 | 花费(美元) |
|---|---|---|---|---|
/threat-model bootstrap |
直连官方 | 36 | 4 分 20 秒 | 1.16 |
/vuln-scan |
直连官方 | 22 | 2 分 51 秒 | 1.05 |
/triage --auto,每条 3 票 |
直连官方 | 60 | 7 分 32 秒 | 5.47 |
/patch --top 4 |
直连官方 | 32 | 3 分 57 秒 | 2.04 |
再扫一遍,--single --no-score |
直连官方 | 15 | 2 分 13 秒 | 0.56 |
| 缩小攻击面,第一轮 | 直连官方 | 27 | 2 分 49 秒 | 0.56 |
| 缩小攻击面,第二轮 | 直连官方 | 46 | 6 分 13 秒 | 1.28 |
| 缩小攻击面,第三轮 | 直连官方 | 35 | 5 分 22 秒 | 1.16 |
交互式 /triage,给了威胁模型,每条 1 票 |
酷站AI | — | 约 12 分钟 | 4.25 |
花费都是 Claude Code 按官方价算的估计,claude -p 取自输出里的 total_cost_usd,交互式那次取自 /cost,和实际账单可能有出入。直连官方的 8 次合计约 13.3 美元。最贵的是分级:它要给每条发现派好几个验证代理,花费跟着发现的条数乘上票数涨。官方文章的提醒在这里很实在:第一次扫描找出的东西会比你预想的多,先给扫描之后的验证、分级、修补留够预算,再考虑多扫几遍。
十、几件要注意的事
- 子代理的模型要用两个变量才固定得住。 只设
CLAUDE_CODE_SUBAGENT_MODEL时,主会话派活时指定的模型优先,见第五节。要统一,再加CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1。 /triage --auto不读威胁模型。 想让分级按你的威胁模型来,就在交互式里回答开头的问题,见第六节。- 一票会摇摆。 同一条发现,3 票全票确认,1 票时被去掉。要省钱可以先 1 票过一遍,被去掉的那几条再用 3 票复核。
- 回归测试要看失败的原因。 见第七节。
- 限制要靠环境,不能只靠提示词。 这四个 skill 只读写文件,官方说在交互式里逐个批准工具调用就不用沙箱;缩小攻击面那几轮要运行代码,我们是在服务器上一个专用的普通用户下跑的,事后又翻了会话记录,确认它们没有监听端口、没有真的发 DNS 查询。即便这样,第三轮还是自己报告说有一次测试把临时目录建到了工作目录外面(测试结束自动删了),提示词里明明写了不要。这正是官方文章那句话:限制要用代码和配置来强制。隔离的几种做法见《安全机制一览》和《沙箱》。
- 被扫的代码是不可信的输入。 代码、注释、提交说明里都可能写着给模型看的指令。参考仓库的补丁代理会把来自目标的文字包起来、提醒模型只当数据看,但它的文档也说这只是缓解,不是保证。补丁一定要人看过再合。Claude Code 对“先
cd进目标目录再跑 git”单独提醒,也是这个道理。 - 会不会被拦。 本站这 9 次运行都正常结束,没有遇到拒绝。Claude 帮助中心的说法是,普通模型可以用来审代码、做威胁模型、修已知问题、在自己的代码里找漏洞;别的一些网络安全工作可能被安全分类器打断,做这类工作的安全从业者可以申请官方的网络安全验证计划。
- 走中转站。 交互式的
/triage走的是酷站AI,问答、后台子代理、检查点都正常,跑完没有报错。参考仓库的.claude/settings.json会给每个请求加一个自定义请求头,经中转站时它有没有被转发,本站没有核对。
十一、该用哪一个
- 平时写代码:装上安全检查插件,Claude 写完就查。
- 想要开箱即用的深度扫描:Claude Security 插件,流程都封装好了。
- 想按自己的威胁模型、自己的标准,每一步都看得见、改得了:用参考仓库的 skill,按威胁模型、扫描、分级、修补的顺序走;分级在交互式里做,把威胁模型交给它。
- 功能开关多、配置项多、有很多“默认关着”的老功能:再照研究文章的三种方法查一遍攻击面。这一步不需要先找到漏洞,挪走的是所有还没被发现的漏洞的入口。
- 修完:补丁过四关,回归测试看失败的原因,最后由人负责合并。之后代码有大改动就再扫一遍,并把新确认的问题写回威胁模型。
这次没有实测的
- 参考仓库的自动流水线(Docker、gVisor、ASAN,默认针对 C/C++ 的内存问题),以及
/customize、/quickstart、/dnr-hunt、/dnr-respond,按 README 写。 /threat-model的interview模式。我们只在/triage里回答了它开头的问题。- 研究文章里官方自己的缩小攻击面 skill 还没有公开,本站用的是照文章的方法自己写的提示词。
- 被扫的是本站写的一个 530 行的小服务。少于 15 个源文件时
/vuln-scan不分头扫,大项目上分头扫描的效果没有测到。 - 每一步只跑了一次。模型的结果有随机性,官方文章也说大代码库会一直零零星星冒出新的发现。
- 只修了分级排在前面的 4 条;缩小攻击面第二、三轮的补丁没有应用和验证。
参考
- Claude 博客:Using LLMs to secure source code(2026-05-27)
- Anthropic 研究,Frontier Red Team:Using Claude to reduce the attack surface of code(2026-10-20)
- 参考仓库:anthropics/defending-code-reference-harness,本站用的是提交 d3bea6b
- Claude Code 文档:Create custom subagents(“Run every subagent on one model”一节)、Run Claude Code programmatically
- Claude 帮助中心:Cyber Verification Program
来源
- Using LLMs to secure source code(Claude 博客,2026-05-27)
- Using Claude to reduce the attack surface of code(Anthropic 研究,Frontier Red Team,2026-10-20)
- anthropics/defending-code-reference-harness(GitHub)
- Create custom subagents(Claude Code 文档)
- Run Claude Code programmatically(Claude Code 文档)
- Cyber Verification Program(Claude 帮助中心)