AI 代码安全审计指南:用 Claude 构建威胁模型、发现并修复漏洞

模型能力在快速进步,但进步并不均匀。我们一直在与安全团队合作,寻找并修复他们自己代码和开源软件中的漏洞。这项工作让我们对如何使用模型来保障源代码安全有了更深入的理解。我们最重要的发现是:漏洞发现现在已经可以轻松并行化,瓶颈已经转移到验证、分类和修复阶段。

这种不平衡有多明显?截至 2026 年 5 月 22 日,我们在开源软件扫描中已经披露了 1,596 个漏洞。据我们所知,其中只有 97 个已被修复。

本文将介绍如何与 Claude Opus 协作,构建威胁模型、在代码库中发现漏洞,然后验证、分类和修复它们。虽然我们并非所有问题都有答案,但会分享团队如何扩展发现阶段的经验,以及后期阶段哪些做法有效。你可以从配套的 defending-code-reference-harness 仓库开始,其中包含交互式工作流的技能和自主扫描的演示框架;我们在每个步骤中会指出对应的技能。

发现-修复循环

发现和修复漏洞最多的团队,最终都收敛到了现有最佳实践的一种变体上。我们将其提炼为六个步骤:

  1. 威胁建模:在开始扫描之前,先定义什么算作漏洞。
  2. 沙箱隔离:构建沙箱环境来隔离智能体并验证漏洞利用。
  3. 发现:让模型在源代码中查找漏洞。
  4. 验证:独立确认哪些发现确实可被利用。
  5. 分类:去重、分配严重程度、确定修复优先级。
  6. 修复:应用补丁、确认漏洞已被消除、搜索变体。

用大语言模型(LLMs)保障源代码安全

威胁建模和沙箱是一次性投入,为防御者循环提供动力——这是一个发现、验证、分类和修复的循环。瓶颈不在于发现漏洞,而在于发现之后的一切。

前两步——构建威胁模型和沙箱——是循环的准备工作。通常每个代码库做一次,当底层系统发生变化时再重新审视。后四步是你对源代码反复执行的循环。

第一次扫描通常会发现最多的问题。后续扫描往往发现更少但更复杂的漏洞,因为简单的那些在之前的扫描中已经被修复了。但不要期望第 N 次扫描时新发现为零——模型是随机的,大型代码库可能有很长的漏洞尾巴,即使代码没有变化也会持续有新发现。

第一次扫描时,应该多次运行循环,根据新发现的数量和你对该系统的风险容忍度来决定何时停止。之后,继续定期扫描,或者在代码发生重大变化时扫描。

1. 威胁建模:定义什么算作漏洞

误报最常见的原因是模型对你的信任边界理解不足。模型可能会因为假设客户端可能发送损坏的值、攻击者可能控制配置而将代码标记为有漏洞,尽管这些输入在你的环境中是受信任的。反过来,模型也可能假设一个面向互联网的服务是内部的,从而漏报真正的漏洞。在这两种情况下,模型错的不是代码,而是威胁模型。

一个团队注意到了一个规律:在有完善文档的威胁模型、系统设计文档、需求和约束的系统上,模型表现最好。当威胁模型定义清晰时,模型的发现"90% 都是可利用的"。

你可以分两步与 Claude 协作构建威胁模型:

第一步,从代码、文档和漏洞历史中引导。把你在第一天会交给新安全工程师的东西喂给模型:架构文档、维基、入口点、git 历史和过去的漏洞。这有助于克服仅从代码推断隐性知识、权衡和设计决策的挑战。然后,让模型创建一个包含系统上下文、资产、入口点和信任边界的威胁模型。最后,让模型对过去的 bug 进行聚类并列出相关的漏洞类别。确保威胁模型记录了你关心和不关心哪些漏洞,以及原因。

一个团队回顾了数百个过去的 CVE 和安全修复提交,将它们提炼成"bug 形态"提示,然后问模型两个问题:修复是否完整?同样的问题是否也存在于其他地方?他们一小时内就发现了三个可利用的问题。正如他们所说:「"过去人们利用了什么"有时是比"在这个代码库中找到漏洞"更简单的成功捷径。」

第二步,让模型采访了解系统的人。可以参考 Shostack 的四个问题:我们在构建什么?可能出什么问题?我们在做什么?我们做得怎么样?先运行引导步骤,这样受访者不用从零开始。这样,他们不用花几个小时研究和从头构建威胁模型,而是从草稿开始。虽然采访步骤是可选的,但它能增加模型无法从代码或文档中获得的上下文,从而改进威胁模型。

一些做法可以带来很大改善:

  • 考虑依赖项的安全策略。许多开源项目会发布安全策略,比如 vLLM 的 security.md、SQLite 的"Defense Against the Dark Arts"和 ImageMagick 的安全策略。你的威胁模型应该直接参考它们,而不是从头重建。
  • 明确什么是受信任的。如果你信任配置文件或已认证的客户端,在威胁模型中记录下来。这些假设有助于将不可利用的 bug 与真正的漏洞利用区分开来。
  • 在代码中包含 THREAT_MODEL.md。放在仓库中,随代码变更更新。发现智能体可以在搜索前先读取它,跳过已知的非问题。

威胁模型会在两个地方用到。在发现阶段,作为范围界定:分割代码、确定目标优先级、跳过超出范围的内容。在分类阶段,作为过滤器:广泛扫描后,用威胁模型更好地校准对你系统和环境的严重程度。

一个扫描大型项目的团队有 40% 的误报率,他们深入调查了原因。发现是可复现的,PoC 也证明了可利用性。但拥有代码的开发团队认为它们是误报,因为这些 bug 不符合项目的威胁模型。另一个团队的 CISO 简洁地说:「(模型)对代码的上下文很好,但对我们的上下文不好。」

技能推荐:试试 threat-model 技能。它会引导完成本节描述的两个步骤——从你的代码、CVE 和 git 历史中推导出草稿,然后通过 Shostack 的四个问题采访系统负责人来完善它。输出是一个 THREAT_MODEL.md 文件,供发现和分类步骤使用。

2. 沙箱:安全运行智能体并验证可利用性

沙箱的一个目的是保护你的系统。要让模型安全自主地运行,你需要一个强隔离层。没有它,智能体可能超出目标范围,做出意想不到的事情。

一个团队告诉模型它没有网络访问权限——但实际上有——模型发现它仍然可以从 GitHub 获取内容。另一个团队观察到一个智能体在扫描过程中回答了 GitHub issue。这些行为都不是恶意的,但都说明了需要通过代码和配置来强制执行约束。

隔离级别要与威胁模型匹配。容器对读取代码的发现智能体来说没问题,但运行目标及其 PoC 时,应该用 microVM(如 Firecracker)或出口锁定的完整 VM,确保没有任何东西能到达你的生产系统。永远不要让智能体能访问凭证(~/.aws、~/.ssh、.env)。

沙箱只在设置阶段给予网络访问权限。拉取依赖、构建、安装工具、部署目标、运行现有测试以确认一切正常。然后,快照环境并移除网络访问。扫描期间,只允许流量到模型 API,通过本地代理路由。每次运行开始时加载快照,确保每次扫描都从相同的干净状态开始。

沙箱的另一个目的是证明可利用性。在静态扫描中,模型读取代码并假设什么可能出错,但无法测试路径是否可达或是否有补偿控制。因此,模型可能会标记出你实际上不关心的不可利用的代码正确性 bug。当团队构建了可以让智能体编译代码、运行测试和引爆 PoC 的沙箱后,不可利用的发现显著减少。

一个攻击性安全团队构建了一个测试框架,给智能体一个测试环境,验证规则很简单:只有当智能体能构建 PoC 并在测试环境上运行时,才是真正的阳性。他们六周后的评估是:"最大的效果杠杆是给模型提供测试环境、活系统,以及运行 PoC。"

构建沙箱时,尽可能固定一切,确保每次运行使用相同的代码和环境:镜像标签、commit SHA、依赖和构建命令。缓存本地副本使构建不需要网络,目标是让容器持久化,以便多个测试循环可以直接加载。

一个团队的扫描标记了一个漏洞,结果发现是智能体下载了旧版本的库,而不是实际部署的版本。这是一个读取了对话记录的工程师发现的,他注意到下载了不同的依赖。他们现在构建 Docker 容器时会将依赖固定为与生产环境匹配,这样发现智能体和验证智能体操作的是与攻击者相同的工件。

构建足够忠实于生产的沙箱很重要。排除依赖(如队列或数据存储)可能导致漏报生产环境中可能存在的 bug。反过来,忽略生产防御(如 WAF 或认证网关)会导致模型报告你的生产环境已经缓解的不可利用发现。

不过,如果因为云依赖、数据存储或其他现实世界的复杂性而无法构建有代表性的沙箱,可以先从发现步骤开始。你不一定需要在沙箱中运行 PoC。前沿模型擅长仅从分析源代码中发现漏洞。几个团队(包括我们自己)都发现这很有效。代价在验证阶段,没有运行中的目标就无法用 PoC 证明发现,所以要为验证预留更多时间。你也可以在发现量足够大时再投入建设沙箱。

参考实现:参见 harness 的 README.md 了解参考沙箱。在该实现中,智能体和目标运行在 gVisor 隔离的容器中,出口锁定到模型 API。目标从固定到特定 commit 的 Dockerfile 构建,setup_sandbox.sh 处理设置阶段。

3. 发现:提供丰富的上下文、简洁的提示和有用的工具

给发现智能体访问它可以根据需要加载的上下文,如威胁模型、架构文档和过去扫描的结果。当智能体理解你的信任边界和系统的实际部署方式时,它能更好地识别特定于你系统的漏洞。

我们发现前沿模型在发现阶段受益于越来越简单的提示。反直觉的是,更具体的提示会让发现变差——长长的检查清单往往会降低模型的创造力,生成更少的新颖 bug。以下是一些在发现阶段有帮助的提示技巧:

  • 提供目标和上下文。说明"为什么"和"什么"——你为什么扫描、什么样的发现是有意义的、正在扫描什么系统——把"如何扫描漏洞"留给模型。前沿模型在安全任务上越来越强,过于具体的指导反而会限制它们的尝试范围。
  • 尝试要求特定的漏洞类别。如果你想专注于由过去 CVE 或代码库语言引导的特定类型漏洞,直接说出来。描述漏洞类别、它的作用和它通常出现在哪里,让模型能在你的代码库中识别它。
  • 定义输出。要求带有预定义字段的结构化报告,并按顺序排列使模型的推理在每个字段上逐步建立。示例字段包括理由、发现、影响、严重程度等。包含一个逃生舱口,让模型可以对弱发现提前退出。

给模型提供搜索和阅读代码库的工具,如 grep、glob 等。也允许模型使用你的团队可能使用的安全特定工具,如 SAST 扫描器或模糊测试器。问模型特定任务需要什么工具并提供它们。最后,让模型按需构建工具:最近的前沿模型越来越擅长编写它们需要的工具。

除了源代码,一个渗透测试团队还给发现智能体提供了发送请求、检查响应和查询流量日志的工具。结果,智能体不需要猜测路径是否可达,可以在运行时针对正在运行的应用程序测试每个候选,将他们的真阳性率提高到接近 100%。

让模型对系统进行第一次遍历,按攻击面、端点或组件等划分搜索空间。然后,将这些分区交给并行的发现智能体,使它们不会收敛到相同的浅层 bug。最后,运行系统级别的遍历,将分区级别的发现作为上下文来搜索漏洞。

尝试暴力发现的团队很快就遇到了收益递减。一个团队说:"我们最初只是水平扩展并发送更多智能体,但看到了有限的回报。"另一个团队增加了焦点区域和并行智能体的数量,得到了"大量问题",其中大部分是彼此的重复。

如果你有沙箱来运行目标,让发现智能体构建发现的 PoC,如脚本、崩溃输入或失败的测试。构建 PoC 有助于智能体迭代和确定发现,工件给验证智能体具体的证据来评估。不过,智能体无法复现的发现仍然可以报告,标记为未证明,以保持高召回率。

技能推荐:vuln-scan 技能在这一阶段很有帮助。它读取你的 THREAT_MODEL.md,将目标划分为焦点区域,并为每个区域分发出并行的审查智能体。输出是结构化的发现,供下一步直接使用。

4. 过滤不可利用的发现

发现优化的是召回率;验证优化的是精确率。换句话说,发现应该找到尽可能多的漏洞——即使是不太可能的——而验证应该排除实际上不可利用的发现。当一个智能体试图在同一步骤中完成这两件事时,它可能会自我审查,排除掉单独验证步骤会确认的真阳性。我们在这方面吃过亏——让发现智能体同时验证发现,导致它们过滤掉了真阳性。

验证智能体应该独立于发现智能体。在没有共享文件系统或对话历史的新容器中运行验证器。如果验证器暴露于发现智能体的推理,它可能只是同意而不是测试这个主张。因此,只给验证器(1)PoC 或书面发现和(2)代码库,让它可以搜索发现者遗漏的缓解措施(如上游验证、认证门、类型约束或不可达代码)。

如果单次验证仍然让太多不可利用的发现通过,尝试运行多个独立的验证器。它们可以考虑不同的角度或使用不同的模型运行。然后,取多数票。也可以考虑用单独的裁判来决定发现和验证智能体的结果。

提示验证智能体去反驳发现智能体的发现。让验证器假设每个发现都是误报,然后搜索发现错误的原因。包含清晰的标准让验证器用来判断发现是否为真阳性。这在发现智能体的输出不包含 PoC 时最重要。目标是尽可能排除不可利用的发现,减少人工审查的工作量。

在我们合作的团队中,添加对抗性验证器大致将发现阶段的不可利用发现率降低了一半。要求该验证器同时构建确认漏洞利用的 PoC,将误报率降至接近零。这两个步骤一起帮助显著减少了下游的分类和修复工作量。

如果你能在沙箱中充分复现生产环境(见步骤 2),提示验证智能体构建并执行可复现的 PoC。如果 PoC 成功,你可以得出结论该发现是可利用的。注意反过来不成立——未能产生有效的 PoC 并不能证明是误报。

一个扫描开源软件包的团队构建了一个有助于闭环的验证步骤:扫描软件包、生成 PoC,然后部署一个使用该软件包并触发 PoC 的模拟应用。他们的看法是:"验证是最大的阻碍,而 PoC 就是验证。"

5. 分类:按根因去重,按前置条件和影响排序

验证确认发现是可利用的,分类则评估修复优先级。以前,发现需要更多精力时,发现 bug 的工程师也会负责分类。现在,模型能在午饭前找到一百个候选,分类成了瓶颈。

恰当的分类有助于防止告警疲劳。如果你提交了太多重复或严重程度虚高的 bug,产品工程师可能会不再阅读它们,即使有些需要立即修复。开源维护者尤其容易被未分类的发现淹没,因为他们收到来自许多不同用户的报告。

多个团队分享了同样的教训:如果我们给产品工程师一堆大部分不可利用的发现,他们会失去对报告的信任并放弃。他们也会优先处理严重和高危发现,以避免压垮下游的工程师。其他团队通过将模型指向他们现有的积压工作——来自之前扫描器、之前模型、赏金计划的未解决发现——在几天内清除了数百个过期项目,取得了成功。

要对发现进行去重,考虑根因。扫描器经常在多个调用站点标记同一个 bug,或报告同一根因的多个症状。以下是一个实用方法:

  1. 先用廉价的确定性检查:相同文件、相同类别、漏洞行号在十行以内。
  2. 然后让模型对剩余内容应用定性规则:
    • 视为重复:相同根因不同表述;同一漏洞在多个调用站点报告;缺少全局保护(如认证检查)按端点报告;原因和结果在同一路径中被标记。
    • 视为不同:同一文件中的不同漏洞类别;不同变量到达不同的汇点;一个辅助函数内的两个独立 bug;两个端点上的相同缺失检查,但每个都需要自己的修复。

如果你的框架为每个发现生成 PoC 和补丁,另一种去重方法是检查一个发现的补丁是否也解除了其他发现的 PoC。

去重后,根据以下因素评估每个发现的严重程度:

  • 可达性。攻击者能从真实的入口点到达这段代码吗?还是只能从内部代码和端点到达?
  • 攻击者控制。不受信任的输入能完整到达汇点吗?还是上游有东西进行了清理或约束?
  • 前置条件。触发 bug 需要什么条件:非默认设置、特定的功能标志、攻击者必须命中狭窄的时间窗口?
  • 认证。未认证的攻击者能触发它吗?还是需要登录用户或管理员?
  • 读 vs 写。攻击者只能读取数据,还是也能修改?
  • 爆炸半径。如果 PoC 触发,谁会受影响?一个用户还是所有用户,一个租户还是平台,用户空间还是内核?

要将评分标准转化为分数,让模型在分配严重程度之前先写出每个问题的答案。先看证据可以防止模型锚定在 bug 类别上("SQL 注入,所以是严重的"),然后将严重程度匹配上去。作为起点:零前置条件加未认证远程访问是严重或高危。一两个前置条件,或需要认证的路径,是中等。三个以上,或仅本地的,是低危。根据你的系统调整阈值。

模型可能会因为上下文不足而虚高严重程度。它们可能不知道攻击者实际控制什么输入,或者看不到补偿控制。以前者为例,如果由未认证请求触发,SQL 注入是严重的;但如果由仅管理员可访问的配置文件触发,则不是问题。对于后者,上游的 WAF 或认证可能无法仅从源代码看到。

解决方案是在分类期间提供威胁模型,告诉模型你在系统中关心和不关心哪些类型的漏洞。例如,说明"我们信任已认证的客户端"可以简化或移除一整类严重问题。

一个团队发现模型经常过度自信,除非有可以验证的基础,或者对威胁模型中什么是预期的有更多上下文。他们的解决方法是给分类智能体与发现智能体相同的威胁模型。

技能推荐:试试 triage 技能。它同时做验证和分类:每个发现的多票验证、跨运行的去重、以及按派生可利用性重新排序。输出是一个简短的、有排序的、有负责人的列表,而不是原始数据。

6. 修复:闭环并为下一个循环改进上下文

修复是闭环并修复漏洞的阶段。它也有助于基于已验证的发现改进威胁模型——更新信任边界或需要更多审查的组件——并将过去的发现输入到下一次扫描的上下文中。每次循环都加固了代码库,使下一次扫描更有依据。

修复前,先写一个用现有代码会失败的新测试。然后,实现修复并确认同一个测试现在通过了,且没有破坏其他东西。(没错,这就是测试驱动开发。)如果不添加测试,修复可能会悄然回退,事后很难证明 bug 确实存在过。

一位渗透测试人员发现他们生成的补丁质量不一——有些好,有些差——直到框架告诉模型通过在修补后的代码上重新运行 PoC 来验证补丁。通过给模型反馈来迭代,补丁质量大幅提升,节省了人工审查的时间。

模型可能只在特定调用站点处理发现,而不是根因。直接提示模型识别并修复根因可以很有效。然后,让模型在两个层级寻找变体:(1)相同模式——其他调用站点或相同 buggy 代码的副本存在于其他地方;(2)相同类别——有一个 SQL 注入漏洞的代码库往往还有更多 SQL 注入漏洞。用已验证的发现和补丁更新威胁模型以闭环。

在发布补丁之前,运行对抗性检查。让一个新的发现智能体以攻击者的角度探测补丁,确认补丁是全面的。然后,简化生成的补丁以处理过于侵入性的补丁。最小化的补丁更容易审查,也更不可能引入新的 bug。提示最小的修复根因的变更——不要重构、不要顺便清理、不要重新格式化。

一个团队最常见的补丁失败:"推荐的补丁往往尽可能严格,以至于会破坏与其他服务的连接。它会解决这个问题,但会破坏让服务正常工作的依赖。"

你可以按以下检查阶梯验证每个补丁,从最廉价的开始:

  1. 构建。补丁编译通过,新测试通过。
  2. 尝试复现。原始 PoC 应该停止工作。这能捕获无效补丁。
  3. 检查回退。原始测试套件仍然通过。这能捕获破坏性或过度限制的补丁。
  4. 重新攻击。一个新的发现智能体运行对抗性检查。这能捕获不完整的补丁。

最后,虽然模型可以编写补丁,但人类仍然需要拥有它。生成的补丁可能以可预测的方式失败——修复症状而不是根因、阻止合法输入、或移除对依赖服务的访问。目标是尽可能验证每个补丁,使人工审查需要更少的精力。目标是帮助开发团队专注于模型可能不知道的细微差别(如即将到来的变更、代码风格),以最少的审查和更新完成补丁。

技能推荐:试试 patch 技能。它消费分类输出,为每个发现生成候选 diff,并由独立的审查智能体检查每个补丁。

开始使用

自己试试这个循环。克隆 defending-code-reference-harness,在 Claude Code 中运行 /quickstart。它会引导你完成交互式工作流,从威胁建模到扫描到分类,在一个演示目标上进行。该仓库还包含一个自主框架和一个 /customize 技能来为你的环境更新框架。

然后,在你自己的代码上运行。选择一个服务或软件包。从代码和文档引导一个威胁模型,完成采访。投入构建你的环境的沙箱。扫描。用独立的智能体验证发现。根据你的标准分类并审查所有评为高危及以上的内容。修复。然后定期重新扫描。

你的第一次扫描会发现比预期更多的问题。大多数需要验证和分类。在为更多扫描预算之前,先为扫描之后的管道预算。

一些可能有用的资源

  • Claude Security:Anthropic 的托管产品,用于智能体漏洞检测和修复。
  • defending-code-reference-harness:配套仓库,包含交互式工作流的技能和自主运行的演示框架。
  • claude-code-security-review action:GitHub Action,将 Claude 作为每个 PR 的安全审查员。
  • 威胁情报富化智能体:构建一个将入侵指标与威胁情报源进行富化的智能体的 Cookbook。
  • 漏洞检测智能体:构建一个威胁建模、扫描漏洞并将发现分类为结构化报告的智能体的 Cookbook。

展望

我们相信模型发现和利用代码中的漏洞正变得越来越容易。因此,作为防御者的工作就是在对手利用之前找到并修复我们代码中的漏洞。一些团队甚至将他们的框架连接到事件:赏金报告触发自动变体分析、安全审查触发扫描并将候选发现附加、已验证的漏洞更新静态分析工具以在未来防止它。

这项工作至关重要且风险很高。但如果做得对,这是一个更大、更有希望的转变的开始——我们能够在攻击者利用漏洞之前找到并修复它们。

致谢:本文由 Eugene Yan 和 Henna Dattani 撰写,Michael Molash、Abel Ribbink、Justin Young、Ben Morris、David Dworken 和 Hasnain Lakhani 有贡献。这项工作基于我们在 Anthropic 使用模型进行安全工作的经验,以及合作伙伴和客户分享的宝贵见解,对此我们深表感谢。

原文:https://claude.com/blog/using-llms-to-secure-source-code