我们分享如何与 Claude Opus 协作构建威胁模型、发现代码库中的漏洞,然后进行验证、分类和修复的最佳实践。
- 分类企业 AI
- 产品未找到任何项目。
- 日期2026 年 5 月 27 日
- 阅读时间5分钟
- https://claude.com/blog/using-llms-to-secure-source-code
模型能力正在快速且不均衡地发展。我们一直在与安全团队合作,在他们自己的代码和开源软件中发现并修复漏洞,这项工作让我们更好地理解了如何使用模型来保护源代码。我们的主要结论是:发现漏洞现在可以轻松并行化,而瓶颈已转移到验证、分类和修复环节。
为了说明这种差异,作为我们自身对开源软件扫描的一部分,截至 2026 年 5 月 22 日,我们已披露了 1,596 个漏洞。据我们所知,其中 97 个已被修复。
本指南将介绍如何与 Claude Opus 协作构建威胁模型、发现代码库中的漏洞,然后进行验证、分类和修复。虽然我们并非掌握所有答案,但我们将分享团队如何扩大发现规模,以及在后续阶段哪些方法行之有效。请立即开始使用随附的代码仓库,其中包含用于交互式工作流的技能和用于自主扫描的演示框架;阅读时我们会指出实现每个步骤的技能。
发现与修复循环
发现和修复最多漏洞的团队最终采用了现有最佳实践的变体。我们将其提炼为六个步骤的序列:
- 威胁模型:在开始扫描之前,确定什么才算作漏洞。
- 沙箱:构建一个沙箱环境来隔离智能体并验证漏洞利用。
- 发现:让模型在源代码中寻找漏洞。
- 验证:独立确认哪些发现实际上是可被利用的。
- 分类:对发现结果去重、分配严重性等级,并确定修复优先级。
- 修复:应用修复方案,确认漏洞已被消除,并搜索同类变体。

前两个步骤——构建威胁模型和沙箱——是为循环的其余部分做准备。这些工作通常每个代码库只做一次,并在底层系统发生变化时重新审视。接下来的四个步骤是你需要针对源代码运行的循环:发现、验证、分类和修复。
对代码库的首次运行通常会产生最多的发现结果。后续运行的发现结果往往更少,但漏洞通常更复杂,因为较简单的漏洞已在之前的运行中被修复。然而,不要指望第 n 次运行会完全没有新的发现。模型具有随机性,而大型代码库可能存在大量长尾漏洞,即使代码未发生变化,这些漏洞也会持续出现。
在对代码库进行首次迭代时,你应该多次运行该循环,并根据净新增发现结果的数量以及你对该系统风险的承受能力来决定何时停止。首次迭代之后,应继续(1)定期进行扫描,或(2)在代码发生实质性变更时进行扫描。
接下来,我们将详细讲解每个步骤,说明其重要性、产出内容以及实施方法。
1. 威胁模型:定义什么算作漏洞
误报最常见的原因是模型对你的信任边界缺乏良好理解。模型可能会将代码标记为存在漏洞,因为它假设客户端可能发送损坏的值,或者攻击者可能控制配置,即使这些输入在你的环境中是受信任的。相反,模型可能假设一个面向互联网的服务仅限内部使用,从而低估了真实的漏洞。在这两种情况下,模型出错的地方在于威胁模型,而非代码本身。
一个团队在其发现结果中注意到一种模式:模型在那些拥有文档完善的威胁模型、系统设计文档、需求和约束条件的系统上表现最佳。当威胁模型定义良好时,模型的发现结果“有 90% 的时间是可被利用的”。
你可以通过两个步骤与 Claude 协作构建威胁模型:
首先,基于代码、文档和漏洞历史进行引导。将你会在第一天交给新安全工程师的资料喂给模型:架构文档、维基页面、入口点、Git 历史记录以及过往漏洞。这有助于克服仅凭代码推断隐性知识、权衡取舍和设计决策的难题。然后,让模型创建一个包含系统上下文、资产、入口点和信任边界的威胁模型。最后,让模型对过往 bug 进行聚类,并列出相关的漏洞类别。确保威胁模型记录了你关注和不关注的漏洞及其原因。
某个团队审查了数百个历史 CVE 和安全修复提交,将其提炼为"bug 形态"提示,并向模型提出两个问题:修复是否完整?是否在所有其他地方都应用了修复?他们在一小时内发现了三个可利用的问题。用他们的话说:"'过去人们利用过什么'有时比'在这个代码库中找漏洞'更容易成为通往成功的捷径。"
其次,让模型与熟悉该系统的人进行访谈。参考 Shostack 的四个问题:我们在构建什么?可能出什么问题?我们对此做了什么?我们做得好吗?先执行引导步骤,这样受访者就不必从零开始。这样一来,他们无需花费数小时从头研究和构建威胁模型,而是可以从草稿入手。虽然访谈步骤是可选的,但它能补充模型无法从代码或文档中获取的上下文,从而改进威胁模型。
一些实践可以带来显著差异:
- 考虑依赖项的安全策略。许多开源项目都会发布安全策略。例如,vLLM 的 security.md、SQLite 的"防御黑魔法"以及 ImageMagick 的安全策略。你的威胁模型应直接参考这些策略,而不是从头重建一套策略。
- 明确标注信任的对象。如果你信任配置文件或经过身份验证的客户端,请在威胁模型中记录这一点。这些假设有助于区分不可利用的 bug 和实际可利用的漏洞。
- 在代码中附带一份 `THREAT_MODEL.md` 文件。将其保存在仓库中,并随着代码变更持续更新。这样,发现智能体在开始搜索前可以先读取该文件,跳过已知的非问题项。
你将在两个环节使用威胁模型。在发现环节,它作为范围界定工具:对代码进行分区、确定目标优先级,并跳过范围之外的内容。这有助于处理那些无法完全扫描的大型代码库。在分类环节,它作为过滤器:在广泛扫描之后,利用威胁模型更好地根据你的系统和环境校准严重等级。
某个团队在扫描一个大型项目时,误报率高达 40%,于是他们深入调查了原因。这些发现是可复现的,概念验证也证明了漏洞的可利用性。但拥有该代码的开发团队却将其视为误报而驳回,因为这些漏洞不符合项目的威胁模型。另一个团队的首席信息安全官则一针见血地指出:"(模型)对代码有很好的上下文理解,但对我们的上下文理解不足。"
试试威胁模型技能。它会引导你完成本节描述的两个步骤——"引导生成"步骤根据你的代码、CVE 和 Git 历史记录推导出草稿;"访谈"步骤则引导系统所有者回答 Shostack 的四个问题来完善模型。输出结果是一个 `THREAT_MODEL.md` 文件,该文件将在发现和分类步骤中使用。
2. 沙箱:安全运行智能体并验证漏洞可利用性
沙箱的一个用途是保护你的系统。为了让模型能够安全、自主地运行,你需要一个强大的隔离层。没有它,智能体可能会超出目标范围,做出一些意料之外的事情。
某个团队告诉模型它没有网络访问权限——但实际上它有——结果模型发现它仍然可以从 GitHub 获取数据。另一个团队观察到,一个智能体在扫描过程中回复了一个 GitHub Issue。这两种行为都不是恶意的,但都表明需要通过代码和配置来强制执行约束。
根据你的威胁模型来匹配隔离级别。对于读取代码的发现智能体,容器就足够了;但运行目标及其概念验证时,应使用微虚拟机(如 Firecracker)或完全虚拟机,并锁定出口流量,确保没有任何东西能触及你的生产系统。并且,切勿让智能体访问任何凭证(如 `~/.aws`、`~/.ssh`、`.env`)。
仅在搭建沙箱时为其提供网络访问权限。拉取依赖项、构建、安装工具、部署目标并运行现有测试,以确认一切正常。然后,对环境进行快照并移除其网络访问权限。在扫描期间,仅允许流量通过本地代理路由至模型 API。每次运行开始时加载该快照,确保每次扫描都从相同的干净状态开始。
沙箱的另一个用途是验证漏洞的可利用性。在静态扫描期间,模型会读取代码并推测可能出问题的地方,但它无法测试某个路径是否可达,或者是否存在补偿性控制措施。因此,模型可能会标记出你实际上并不关心的、不可利用的代码正确性缺陷。当团队构建了一个沙箱,让智能体能够编译代码、运行测试并引爆概念验证时,不可利用的发现结果显著减少了。
一个攻击性安全团队构建了一个测试框架,为智能体提供测试环境,并附带一条简单的验证规则:只有当智能体能够构建概念验证并在测试环境中运行它时,才算真正的阳性发现。他们六周后的评估是:“最大的效能杠杆在于为模型提供测试环境、真实系统,并运行 PoC。”
在构建沙箱时,尽可能固定所有内容,使每次运行都在相同环境中使用相同代码:镜像标签、提交 SHA、依赖项和构建命令。缓存本地副本,使构建无需网络,并力求容器具有持久性,以便多个测试循环可以直接加载它。
一个团队的扫描标记了一个漏洞,结果发现这是智能体下载了旧版本库而非实际部署版本导致的副产品。一名工程师在阅读日志时发现正在下载不同的依赖项,从而捕获了此问题。他们现在构建的 Docker 容器会固定依赖项以匹配生产环境,这样发现漏洞的智能体和验证智能体就能在攻击者会操作的同一工件上进行操作。
构建足够贴近生产环境的沙箱至关重要。排除依赖项(如队列或数据存储)可能导致对生产环境中可能存在的漏洞报告不足。反之,忽略生产环境中的防御措施(如 WAF 或身份验证网关)则会导致模型报告那些你的生产环境已经缓解、无法实际利用的发现。
然而,如果由于云依赖、数据存储或其他现实世界的复杂性而无法构建具有代表性的沙箱,那么可以转而从发现步骤(如下所述)开始。你并不一定需要在沙箱中运行概念验证。前沿模型仅通过分析源代码就能很好地发现漏洞。包括我们团队在内的多个团队都发现这种方法很有效。其权衡之处在于验证阶段:如果没有运行中的目标,我们就无法通过概念验证来证明发现,因此需要为验证分配更多时间。你也可以在发现的漏洞数量证明其必要性之后,再投入资源构建沙箱。
请参考 harness 的 README.md 文件以获取一个参考沙箱。在该实现中,智能体和目标运行在 gVisor 隔离的容器内,其出口流量被锁定到模型 API。目标基于一个固定到特定提交的 Dockerfile 构建,并由 `setup_sandbox.sh` 处理设置阶段。
3. 发现:提供丰富的上下文、更短的提示词和有用的工具
让发现智能体能够访问它可以根据需要加载的上下文,例如威胁模型、架构文档以及过往扫描的结果。当智能体理解你的信任边界以及系统实际部署方式时,它就能更好地识别出你系统特有的漏洞。
我们发现,在发现阶段,前沿模型受益于日益简化的提示词。与直觉相反,更具指令性的提示词反而会使发现效果变差——冗长的检查清单往往会降低模型的创造力,并产生更少的新颖漏洞。以下是一些在发现阶段有所帮助的提示词技巧:
- 提供目标和背景。说明“为什么”和“是什么”——为什么进行扫描、有意义的发现是什么样的、正在扫描什么系统——而把“如何扫描漏洞”留给模型。前沿模型在安全任务上越来越出色,过度规定具体方法反而会限制它们的尝试范围。
- 尝试要求针对特定漏洞类别。如果你希望根据以往的 CVE 或代码库的语言来聚焦于某类漏洞,请明确说明。描述该漏洞类别、它的作用以及通常出现在哪里,这样模型就能在你的代码库中识别它。
- 定义输出格式。要求生成一份包含预定义字段的结构化报告,并按顺序排列,使模型的推理能够基于每个字段逐步展开。示例字段包括:理由、发现、影响、严重程度等。同时设置一个退出机制,让模型可以在发现较弱时提前结束。
为模型提供搜索和阅读代码库的工具,例如 grep、glob 等。同时允许模型使用你的团队可能使用的安全专用工具,如 SAST 扫描器或模糊测试工具。询问模型完成特定任务需要哪些工具,并提供这些工具。最后,让模型根据需要自行构建工具:最近的前沿模型在编写所需工具方面越来越擅长。
除了源代码之外,一个渗透测试团队还为发现智能体提供了发送请求、检查响应以及查询流量日志的工具。结果,智能体无需猜测某个路径是否可达,可以在运行过程中针对实际运行的应用程序逐一测试每个候选路径,从而将真阳性率提升至接近 100%。
让模型先对系统进行一轮初步扫描,以划分搜索空间,例如按攻击面、端点或组件进行划分。然后,将这些划分后的区域分配给并行的发现智能体,避免它们都集中在相同的浅层漏洞上。最后,进行一次系统级扫描,将各分区的发现结果作为上下文,用于搜索漏洞。
那些试图通过蛮力进行发现的团队很快就遇到了收益递减。其中一个团队表示:“我们最初尝试横向扩展并派出更多智能体,但发现收益有限。”另一个团队增加了关注领域和并行智能体的数量,结果出现了“大量问题”,其中大部分是彼此重复的。
如果你有一个沙箱来运行目标程序,请让发现智能体为该发现构建一个概念验证,例如一个脚本、一个导致崩溃的输入或一个失败的测试。构建概念验证有助于智能体迭代并锁定该发现,而该产物则为验证智能体提供了具体的证据来进行评估。尽管如此,智能体无法复现的发现仍然可以上报,并标记为未经证实,这样就能保持较高的召回率。
漏洞扫描技能在此阶段很有帮助。它会读取你的 THREAT_MODEL.md 文件,将目标程序划分为多个关注领域,并为每个领域派出并行的审查智能体。其输出是结构化的发现,可供后续步骤直接使用。
4. 验证:过滤掉不可利用的发现
发现阶段优化的是召回率;验证阶段优化的是精确率。换句话说,发现阶段应尽可能多地找出漏洞——即使是可能性不大的漏洞——而验证阶段则应排除那些实际上不可利用的发现。当一个智能体试图在同一步骤中同时完成这两项任务时,它可能会自我审查,从而排除掉可被利用的真阳性。我们在这方面吃过苦头,要求发现智能体同时进行验证,导致它们过滤掉了那些本可以通过单独的验证步骤确认的真阳性。
验证智能体应与发现智能体相互独立。在一个全新的容器中运行验证智能体,不要共享文件系统或对话历史。如果验证智能体接触到了发现智能体的推理过程,它可能会简单地表示同意,而不是去测试该论断。因此,只给验证智能体提供(1)概念验证或书面发现,以及(2)代码库,这样它就能搜索发现者遗漏的缓解措施(例如,上游验证、认证网关、类型约束或不可达代码)。
如果单次验证仍然放过太多不可利用的发现,可以尝试运行多个独立的验证器。它们可以从不同角度进行考量,或使用不同的模型来运行。然后,采取多数投票制。同时,考虑设置一个独立的裁判,用于裁决发现智能体与验证智能体给出的结果。
提示验证智能体去反驳发现智能体的发现。让验证器假设每个发现都是误报,并寻找该发现错误的理由。包含清晰的评判标准,供验证智能体用于判断该发现是否为真实发现。当发现智能体的输出不包含概念验证(PoC)时,这一点最为重要。目标是尽可能排除不可利用的发现,以减少人工审查的工作量。
在我们合作过的团队中,加入一个对抗性验证器,大致能将发现阶段产生的不可利用发现率减半。要求该验证器同时构建一个确认漏洞利用的概念验证,则能将误报率降至接近零。这两步结合起来,显著减轻了下游的初步分类和补丁修复工作负担。
如果你能够在沙盒中充分复现你的生产环境(参见步骤 2),则可以提示验证智能体构建并执行一个可复现的概念验证(PoC)。如果 PoC 成功,你可以断定该发现是可利用的。请注意,反之则不成立——未能生成有效的 PoC 并不能证明该发现是误报。
一个扫描开源软件包的团队构建了一个验证步骤,帮助形成了闭环:扫描软件包,生成概念验证,然后部署一个使用该软件包并触发 PoC 的模拟应用程序。他们的看法是:“验证是最大的瓶颈,而 PoC 就是验证本身。”
5. 初步分类:按根本原因去重,按前提条件和影响排序
验证确认了发现的可利用性,而初步分类则评估修复的优先级。过去,当发现工作需要更多精力时,发现漏洞的工程师也会同时负责分类。如今,模型能在午饭前就发现上百个候选漏洞,初步分类便成了瓶颈。
合理的分类有助于防止告警疲劳。如果你提交了太多重复或严重程度被夸大的漏洞,产品工程师可能会停止阅读它们,即使是那些需要立即修补的漏洞。开源维护者尤其容易被未经分类的发现压垮,因为他们会收到来自许多依赖其软件的不同用户的报告。
多个团队分享了同样的经验教训:如果我们向产品工程师提交一堆发现,其中大部分是不可利用的,他们就会对这些报告失去信任并放弃。他们还优先处理严重和高风险的发现,以避免压垮下游的工程师。其他团队通过让模型指向他们现有的积压工作——来自先前扫描器、先前模型、漏洞赏金计划的未处理发现——取得了成功,并在数天内清理了数百个陈旧条目。
为了对发现进行去重,需要考虑根本原因。扫描器常常在多个调用点标记同一个漏洞,或者报告单个根本原因的多个症状。这里有一种实用的方法:首先,使用一个廉价且确定性的过滤:相同文件、相同类别、漏洞行号彼此相差十行以内。然后,让模型对剩余部分应用定性规则:
- 视为重复:同一根本原因的不同表述;在多个调用点报告的同一漏洞;按端点报告的缺失全局保护(如身份验证检查);或在同一条路径中被标记的原因及其后果。
- 视为不同:同一文件中的不同漏洞类别;到达不同汇点的不同变量;一个辅助函数内的两个独立错误;两个端点上缺失的同一检查,但每个都需要各自的修复。
如果你的测试框架为每个发现生成了概念验证代码和补丁,另一种去重方法是检查一个发现的补丁是否也能解除其他发现的概念验证代码。
去重之后,根据以下因素对每个发现的严重程度进行评级:
- 可达性。攻击者能否从真实的入口点到达这段代码,还是它只能从内部代码和端点到达?
- 攻击者控制程度。不受信任的输入是否完整地到达了汇点,还是上游的某些环节对其进行了清理或约束?
- 前置条件。漏洞触发需要满足哪些条件:是否存在非默认设置、特定的功能开关,或者攻击者必须抓住的狭窄时间窗口?
- 身份认证。未认证的攻击者能否触发该漏洞,还是需要已登录用户或管理员权限?
- 读取与写入。攻击者只能读取数据,还是也能修改数据?
- 影响范围。如果概念验证攻击成功,谁会受到影响?是单个用户还是所有用户,单个租户还是整个平台,用户态还是内核态?
要将评估标准转化为评分,先让模型写出每个问题的答案,再分配严重等级。先梳理证据可以防止模型锚定于漏洞类别(“SQL注入,所以是严重”),然后人为拔高严重等级以匹配预期。作为起点:零前置条件且无需认证的远程访问属于严重或高危;存在一至两个前置条件,或需要认证的攻击路径,属于中危;三个及以上前置条件,或仅限本地访问,属于低危。请根据你的系统调整这些阈值。
模型可能因上下文不足而拔高严重等级。它们可能不了解攻击者实际能控制哪些输入,或者看不到补偿性控制措施。前者举例:SQL注入如果由未认证请求触发则属于严重,但如果仅由管理员专属配置文件触发则无关紧要。后者举例:上游的WAF或身份认证等可阻止漏洞利用的机制,可能无法仅从源代码中看到。
解决方案是在分类排查阶段提供一个威胁模型,告诉模型在你的系统中哪些类型的漏洞需要关注、哪些不需要。例如,明确说明“我们信任已认证的客户端”,就可以简化或消除一整类严重漏洞。
有一个团队发现,除非模型有可验证的依据,或者对威胁模型中哪些行为属于预期行为有更多上下文,否则模型往往过于自信。他们的修复方法是:给分类排查智能体提供与发现智能体相同的威胁模型。
试试分类排查技能。它同时执行验证和分类排查:对每个发现进行多轮投票验证、跨运行去重,并根据推导出的可利用性重新排序。输出的是一个简短、有优先级排序、已认领的列表,而非原始数据转储。
6. 补丁修复:形成闭环,为下一轮循环改进上下文
补丁修复阶段需要形成闭环并修复漏洞。它还有助于根据验证后的发现来改进威胁模型——更新需要更严格审查的信任边界或组件——并将过往发现纳入下一轮扫描的上下文。每一轮循环都会强化代码库,并使下一次扫描掌握更充分的信息。
在打补丁之前,先编写一个针对现有代码会失败的新测试。然后,实施修复,并确认同一个测试现在能通过,且没有破坏其他任何功能。(没错,这就是测试驱动开发。)如果不添加测试,修复可能会在不知不觉中退化,并且事后很难证明该漏洞确实存在过。
一位渗透测试人员发现,他们生成的补丁质量参差不齐——有些好,有些差——直到测试框架让模型通过在打过补丁的代码上重新运行概念验证来验证补丁。通过让模型获得反馈进行迭代,补丁质量大幅提升,节省了人工审查的时间。
模型可能会狭隘地处理特定调用点上的发现,而不是根本原因。简单地提示模型识别并修复根本原因可能很有效。然后,让模型在两个层面寻找变体:(1)相同模式,即代码库中其他地方存在相同的错误代码调用点或副本;(2)相同类别,即存在一个 SQL 注入漏洞的代码库往往存在更多 SQL 注入漏洞。用经过验证的发现和补丁更新威胁模型,以形成闭环。
在发布补丁之前,运行一次对抗性检查。让一个新的发现智能体以攻击者的身份探查该补丁,以确认补丁是全面的。然后,简化生成的补丁,以处理那些过于侵入性的补丁。最小化的补丁更容易审查,也更不容易引入新错误。提示词应要求做出能修复根本原因的最小改动——不重构、不附带清理、不重新格式化。
某个团队在描述他们最常见的补丁失败情况时表示:“推荐的补丁往往尽可能严格,以至于会中断与其他服务的连接。它虽然能解决问题,但会破坏让该服务得以正常运行的那些依赖关系。”
你可以根据一系列检查项来验证每个补丁,从成本最低的开始:
- 构建。补丁能够编译通过,并且新的测试用例也能通过。
- 尝试复现。原始的 PoC(概念验证)应该不再有效。这能捕获无效的补丁。
- 检查回归。原始的测试套件仍然能够通过。这能捕获有缺陷或限制过严的补丁。
- 重新攻击。一个全新的发现智能体会运行一次对抗性检查。这能捕获不完整的补丁。
最后,尽管模型可以编写补丁,但最终仍需由人来负责。生成的补丁可能会以可预见的方式失败——治标不治本、阻止合法输入,或移除对某个依赖服务的访问权限。目标是尽可能充分地验证每个补丁,从而让人工审查所需的工作量更少。目标是帮助开发团队专注于模型可能不了解的细微之处(例如,即将发生的变更、代码风格),同时只需对补丁进行最少的审查和更新。
尝试使用补丁技能。它会消耗分类输出,并为每个发现生成一个候选差异补丁,同时由一个独立的审查智能体检查每个补丁。
开始使用
尝试亲自运行这个循环。克隆 `defending-code-reference-harness` 仓库,并在 Claude Code 中运行 `/quickstart`。它会引导你在一个演示目标上完成从威胁建模、扫描到分类的交互式工作流程。该仓库还包含一个自主运行框架和一个 `/customize` 技能,用于根据你的环境更新该框架。
然后,在你自己的代码上运行它。选择一个服务或软件包。根据代码和文档引导出一个威胁模型,并进行访谈。投入精力为你的环境构建一个沙箱。进行扫描。使用一个独立的智能体验证发现结果。根据你的标准进行分类,并审查所有评级为高及以上的内容。打补丁。然后定期重新扫描。
你的首次扫描会发现比你预期更多的结果。其中大部分需要验证和分类。在预算更多扫描之前,请先为扫描后的处理流程做好预算。
以下是一些可能对你有帮助的资源:
- Claude Security:Anthropic 针对智能体漏洞检测与修复的托管产品。
- defending-code-reference-harness:配套代码仓库,包含用于交互式工作流的技能,以及用于自主运行的演示框架。
- claude-code-security-review action:一个 GitHub Action,可在每次拉取请求中让 Claude 担任安全审查员。
- 威胁情报增强智能体:构建智能体的指南,该智能体可针对威胁情报源丰富入侵指标。
- 漏洞检测智能体:构建智能体的指南,该智能体可构建威胁模型、扫描漏洞,并将发现结果分类整理成结构化报告。
展望未来
我们相信,模型发现和利用代码漏洞正变得越来越容易。因此,我们作为防御者的工作就是在攻击者利用漏洞之前,发现并修复代码中的漏洞。有些团队甚至将他们的测试框架与事件联动起来,例如,一份漏洞赏金报告触发自动变体分析,一次安全审查触发扫描并附带候选发现结果,或者一个已验证的漏洞更新静态分析工具以防止其再次发生。
这项工作至关重要且风险极高。但如果做得好,它将开启一个更宏大、更充满希望的转变的开端,届时我们将能够在攻击者利用漏洞之前发现并修复它们。
如果你想持续关注我们在网络安全方面的工作,请在此处注册我们的邮件列表。
致谢
本文由 Eugene Yan 和 Henna Dattani 撰写,感谢 Michael Molash、Abel Ribbink、Justin Young、Ben Morris、David Dworken 和 Hasnain Lakhani 的贡献。这项工作借鉴了我们在 Anthropic 使用模型进行安全工作的经验,以及我们的合作伙伴和客户分享的宝贵见解,对此我们深表感激。
借助 Claude 改变您组织的运作方式
We share best practices for how you can work with Claude Opus to build a threat model, discover vulnerabilities in your codebase, then verify, triage, and patch them.
- Category
- ProductNo items found.
- DateMay 27, 2026
- Reading time5min
- https://claude.com/blog/using-llms-to-secure-source-code
Model capabilities are advancing quickly, and unevenly. We’ve been working with security teams to find and fix vulnerabilities in their own code and open source software, and the work has given us a better understanding of how to use models to secure source code. Our primary takeaway: discovery is now straightforward to parallelize, and the bottleneck has shifted to verification, triage, and patching.
To give some indication of this discrepancy, as part of our own scanning of open source software, as of May 22, 2026, we had disclosed 1,596 vulnerabilities. To our knowledge, 97 of these have been patched.
This guide walks through how you can work with Claude Opus to build a threat model, discover vulnerabilities in your codebase, then verify, triage, and patch them. While we don’t have all the answers, we’ll share how teams have scaled discovery and what’s helped in the later stages. Get started today with the accompanying repo which includes skills for interactive workflows and a demo harness for autonomous scanning; we’ll call out the skill that implements each step as you read.
The find-and-fix loop
Teams finding and fixing the most vulnerabilities converged on a variation of existing best practices. We’ve distilled them into a sequence of six steps:
- Threat model: Decide what counts as a vulnerability before you start scanning.
- Sandbox: Build a sandbox environment to isolate agents and prove exploits.
- Discovery: Have models look for vulnerabilities in your source code.
- Verification: Independently confirm which findings are actually exploitable.
- Triage: Deduplicate findings, assign severity, and prioritize what needs fixing.
- Patching: Apply the fix, confirm the vulnerability is nullified, and search for variants.

The first two steps—building a threat model and a sandbox—are the setup for the rest of the loop. These are typically done once per codebase and revisited when the underlying system changes. The next four steps are the loop you’ll run against the source: discover, verify, triage, and patch.
The first run on a codebase typically has the highest number of findings. Subsequent runs tend to have fewer—though often more complex—vulnerabilities, as the simpler ones were patched in prior runs. However, don’t expect the nth run to have zero new findings. Models are stochastic, and a large codebase can have a long tail of vulnerabilities that continue to trickle in even when the code is unchanged.
On your first iteration with a codebase, you should run the loop multiple times, deciding when to stop based on the number of net-new findings and your risk tolerance for that system. After that first iteration, continue to scan (1) periodically or (2) whenever the code meaningfully changes.
Next, we’ll walk through each step in detail, explaining why it matters, what it produces, and how to implement it.
1. Threat model: Define what counts as a vulnerability
The most common cause of false positives is that the model lacks a good understanding of your trust boundaries. The model might flag code as vulnerable because it assumes a client could send corrupted values or an attacker could control the config, even though these inputs are trusted in your environment. Conversely, the model might assume that an internet-facing service is internal-only and thus under-report true vulnerabilities. In both cases, the model is wrong about the threat model, not the code.
One team noticed a pattern across their findings: the model performed best on systems with well-documented threat models, system design docs, requirements, and constraints. When the threat model was well-defined, the model's findings "were exploitable 90 percent of the time."
You can work with Claude to build a threat model in two steps:
First, bootstrap from the code, docs, and vulnerability history. Feed the model what you would hand a new security engineer on day one: architecture docs, wikis, entry points, git history, and past vulnerabilities. This helps overcome the challenge of inferring implicit knowledge, trade-offs, and design decisions from code alone. Then, ask the model to create a threat model that includes the system context, assets, entry points, and trust boundaries. Finally, have the model cluster past bugs and list the relevant vulnerability classes. Make sure the threat model documents what vulnerabilities you do and don’t care about, and why.
One team reviewed hundreds of past CVE and security-fix commits, distilled them into "bug-shape" hints, and asked the model two questions: was the fix complete, and was it applied everywhere else? They found three exploitable issues in an hour. As they put it: "'What have people exploited in the past' is sometimes a much easier cheat-code towards success than 'find me vulnerabilities in this codebase.'"
Second, have the model interview someone who knows the system well. Consider Shostack's four questions: What are we building? What can go wrong? What are we doing about it? Did we do a good job? Run the bootstrap step first so the interviewee isn’t starting from scratch. This way, instead of spending hours researching and building a threat model from scratch, they can start from a draft. And while the interview step is optional, it adds context the model can’t get from the code or docs, which improves the threat model.
A few practices can make a big difference:
- Consider your dependencies’ security policies. Many open-source projects publish one. For example, vLLM’s
security.md, SQLite's "Defense Against the Dark Arts", and ImageMagick's security policy. Your threat model should consider them directly instead of rebuilding a policy from scratch. - Name what is trusted. If you trust config files or authenticated clients, document it in the threat model. These assumptions help separate non-exploitable bugs from actual exploits.
- Include a
THREAT_MODEL.mdwith the code. Have it in the repo and update it as code changes. The discovery agent can then read it before searching, skipping known non-issues.
You’ll use the threat model in two places. In discovery, as scope: partition the code, prioritize targets, and skip what is out of scope. This helps with large codebases you cannot scan entirely. In triage, as a filter: after scanning broadly, use the threat model to better calibrate severity to your system and environment.
One team scanning a large project had a 40% false positive rate and dug into why. The findings were reproducible and the PoCs proved exploitability. But the dev team who owned the code dismissed them as false positives because the bugs didn't fit the project's threat model. Another team's CISO put it succinctly: "[The model has] good context of the code, but not good context of us."
Try the threat-model skill. It walks through both steps described in this section—bootstrap derives a draft from your code, CVEs, and git history, and interview walks a system owner through Shostack’s four questions to refine it. The output is a THREAT_MODEL.md file which is used in the Discovery and Triage steps.
2. Sandbox: Run agents safely and verify exploitability
One purpose of the sandbox is to protect your systems. To enable models to run safely and autonomously, you need a strong isolation layer. Without it, the agent may overshoot the target and do something unexpected.
One team told the model it had no network access—when it actually did—and the model discovered it could fetch from GitHub anyway. Another team observed an agent answer a GitHub issue mid-scan. Neither action was malicious, but both demonstrated the need to enforce constraints via code and configuration.
Match the isolation to your threat model. Containers are fine for the discovery agent reading code, but run the target and its PoCs in a microVM (like Firecracker) or a full VM with egress locked down so nothing can reach your production systems. And never have credentials (~/.aws, ~/.ssh, .env) available to the agent.
Give the sandbox network access only while you’re setting it up. Pull the dependencies, build, install tools, deploy the target, and run the existing tests to confirm everything works. Then, snapshot the environment and remove its network access. During scanning, allow traffic only to the model API, routed through a local proxy. Load the snapshot at the start of each run so every scan begins from the same clean slate.
Another purpose of the sandbox is to prove exploitability. During static scanning, the model reads code and hypothesizes what might break, but it cannot test if a path is reachable or if there's a compensating control. As a result, the model might flag unexploitable code-correctness bugs that you don’t actually care about. When teams built a sandbox where the agent could compile code, run tests, and detonate a proof of concept, non-exploitable findings dropped significantly.
One offensive-security team built a harness that gives the agent a test bed, with a simple verification rule: it’s only a true positive if the agent can build a proof of concept and run it on the test bed. Their assessment after six weeks was that "the biggest efficacy lever has been giving the model test beds, live systems, and running the PoCs."
When building sandboxes, pin as much as you can so every run uses the same code in the same environment: image tags, commit SHAs, dependencies, and build commands. Cache a local copy so the build requires no network, and aim for the container to be durable so multiple testing loops can just load it.
One team's scan flagged a vulnerability that turned out to be a byproduct of the agent downloading an older version of the library instead of what was actually deployed. This was caught by an engineer who read the transcript and spotted that a different dependency was being downloaded. They now build Docker containers with dependencies pinned to match production, so the finding agent and the verification agent operate on the same artifacts an attacker would.
It’s important to build sandboxes that are faithful enough to production. Excluding dependencies (like a queue or datastore) can lead to under-reporting bugs that may exist in production. Conversely, ignoring production defenses (like a WAF or auth gateway) leads to the model reporting unexploitable findings that your prod environment already mitigates.
Nonetheless, if building a representative sandbox is impractical because of cloud dependencies, data stores, or other real-world complexities, start with the discovery step (below) instead. You don’t necessarily need to run PoCs in a sandbox. Frontier models are good at finding vulnerabilities from just analyzing source code. Several teams, including our own, have found this effective. The trade-off is in the verification phase, where without a running target we can’t prove findings with a PoC, so budget more time for verification. You can also invest in the sandbox later, once the volume of findings justifies it.
Refer to the harness README.md for a reference sandbox. In this implementation, agents and targets run in gVisor-isolated containers with egress locked to the model API. The target is built from a Dockerfile pinned to a specific commit, with setup_sandbox.sh handling the setup phase.
3. Discovery: Provide rich context, shorter prompts, and useful tools
Give the discovery agent access to context it can load as needed, such as the threat model, architecture docs, and results of past scans. When the agent understands your trust boundaries and how the system is actually deployed, it can better identify vulnerabilities specific to your system.
We’ve found frontier models to benefit from increasingly simple prompts during the discovery phase. Counterintuitively, more prescriptive prompts make discovery worse—long checklists tend to reduce the model’s creativity and generate fewer novel bugs. Here are some prompting tips that helped in the discovery phase:
- Provide the goal and context. Indicate the “why” and “what”—why you’re scanning, what a finding that matters looks like, what system is being scanned—and leave “how to scan for vulnerabilities" to the model. Frontier models are increasingly good at security tasks and being overly prescriptive can narrow what they try.
- Try asking for a specific vulnerability class. If you’d like to focus on a specific type of vulnerability guided by prior CVEs or the codebase’s language, say that. Describe the vulnerability class, what it does and where it tends to live, so the model can recognize it in your codebase.
- Define the output. Ask for a structured report with predefined fields, and order them so the model’s reasoning builds on each field. Example fields include rationale, finding, impact, severity, etc. Include an escape hatch so the model can exit early for weak findings.
Give the model tools to search through and read the codebase, such as grep, glob, etc. Also let the model use security-specific tools your team might use such as SAST scanners or fuzzers. Ask the model what tools are needed for a specific task and make them available. Finally, let the model build tools as needed: recent frontier models are increasingly good at writing the tools they need.
In addition to source code, one pentesting team gave the discovery agent tools to send requests, check the responses, and query traffic logs. As a result, the agent didn’t need to guess whether a path could be reached and could test each candidate against the running application as it went, improving their true-positive rate to nearly 100 percent.
Have the model do a first pass over the system to partition the search space, such as by attack surface, endpoint, or component. Then, feed those partitions to parallel discovery agents so they don’t converge on the same shallow bugs. Finally, run a system-level pass that takes the partition-level findings as context to search for vulnerabilities.
Teams that tried to brute-force discovery quickly hit diminishing returns. From one team: "We initially tried to just horizontally scale and send more agents, but saw limiting returns." Another increased the number of focus areas and parallel agents and got "tons of issues", most of them duplicates of each other.
If you have a sandbox to run the target, ask the discovery agent to build a PoC of the finding, such as a script, a crashing input, or a failing test. Building the PoC helps the agent iterate and pin down the finding, and the artifact gives the verification agent concrete evidence to evaluate. Nonetheless, findings the agent can’t reproduce can still be reported, flagged as unproven, so you keep recall high.
The vuln-scan skill is helpful in this stage. It reads your THREAT_MODEL.md, partitions the target into focus areas, and fans out parallel review agents per area. The output is structured findings the next steps consume directly.
4. Verification: Filter out non-exploitable findings
Discovery optimizes for recall; verification optimizes for precision. In other words, discovery should find as many vulnerabilities as possible—even unlikely ones—and verification should exclude findings that are not actually exploitable. When an agent tries to do both in the same step, it can self censor and exclude exploitable true positives. We learned this the hard way, where asking discovery agents to also verify findings led to them filtering out true positives that a separate verification step would have confirmed.
The verifier agent should be independent from the discovery agent. Run the verifier in a fresh container without a shared filesystem or conversation history. If the verifier is exposed to the discovery agent’s reasoning, it may simply agree instead of testing the claim. Thus, give the verifier only (1) the proof of concept or written finding and (2) the codebase, so it can search for mitigations the finder missed (e.g., upstream validation, auth gates, type constraints, or unreachable code).
If a single verification pass still lets too many unexploitable findings through, try running multiple independent verifiers. They can consider different angles or run with different models. Then, take the majority vote. Also consider having a separate judge to decide between the discovery and verification agents’ results.
Prompt the verification agent to disprove the discovery agent’s findings. Have the verifier assume each finding is a false positive and search for reasons the finding is wrong. Include clear criteria that the verifier agent can use to determine if the finding is a true positive. This matters most when the discovery agent’s output doesn’t include a PoC. Aim to exclude as many non-exploitable findings as possible to reduce effort on manual reviews.
Across the teams we’ve worked with, adding an adversarial verifier roughly halved the rate of non-exploitable findings from the discovery phase. Requiring that verifier to also build a proof of concept confirming the exploit brought the false positive rate to near zero. Together, these two steps helped to reduce the downstream triage and patching load significantly.
If you’re able to sufficiently reproduce your production environment in a sandbox (see step 2), prompt the verifier agent to build and execute a reproducible proof of concept (PoC). If the PoC works, you can conclude the finding is exploitable. Note that the inverse isn’t true—failure to produce a working PoC is not proof of a false positive.
One team scanning open-source packages built a verification step that helped to close the loop: scan the package, generate a proof of concept, then deploy a mock application that uses the package and triggers the PoC. Their take was that: "Validation is the biggest holdup and the PoC is the validation."
5. Triage: Deduplicate by root cause, rank by preconditions and impact
While verification confirms a finding is exploitable, triage assesses patching priority. Previously, when discovery took more effort, the engineer who found the bug also triaged it. Now, with models capable of finding a hundred candidates before lunch, triage has become the bottleneck.
Proper triage helps prevent alert fatigue. If you submit too many bugs that are duplicated or have an inflated severity, product engineers may stop reading them, even the ones that need immediate patching. Open source maintainers are especially likely to be overwhelmed by untriaged findings since they receive reports from many different users that rely on their software.
Multiple teams shared the same lesson: if we send product engineers a pile of findings where a majority are non-exploitable, they will lose trust in the reports and give up. They also prioritize critical and high findings to avoid overwhelming the engineers downstream. Other teams found a win by pointing the model at their existing backlog—open findings from prior scanners, prior models, bug-bounty intake—and cleared hundreds of stale items in days.
To deduplicate findings, consider the root cause. Scanners often flag one bug at multiple call sites or report multiple symptoms of a single root cause. Here’s one practical approach: First, use a cheap deterministic pass: same file, same category, vulnerability line numbers within ten lines of each other. Then, have a model apply qualitative rules to what remains:
- Treat as duplicate: the same root cause worded differently; the same vulnerability reported at multiple call sites; a missing global protection (like an auth check) reported per endpoint; or a cause and its consequence flagged in the same path.
- Treat as distinct: different vulnerability classes in the same file; different variables reaching different sinks; two independent bugs inside one helper; the same missing check on two endpoints, but each requires its own fix.
If your harness generates PoCs and patches for each finding, another approach to deduplicate findings is to check if the patch for one finding also disarms the PoCs of others.
After deduplication, rate the severity of each finding based on:
- Reachability. Can an attacker reach this code from a real entry point, or is it only reachable from internal code and endpoints?
- Attacker control. Does untrusted input reach the sink intact, or does something upstream sanitize or constrain it?
- Preconditions. What has to be in place for the bug to trigger: a non-default setting, a specific feature flag, a narrow time window the attacker has to hit?
- Authentication. Can an unauthenticated attacker trigger it, or does it require a logged-in user or an admin?
- Read vs. write. Can the attacker only read data, or also modify it?
- Blast radius. If the PoC fires, who is affected? One user or all users, one tenant or the platform, userland or the kernel?
To turn the rubric into a score, have the model write out its answer to each question before assigning a severity. Going through the evidence first keeps the model from anchoring on the bug class (“SQL injection, so critical”) and then inflating the severity to match. As a starting point: zero preconditions with unauthenticated remote access is critical or high severity. One or two preconditions, or an authenticated path, is medium. Three or more, or local-only, is low. Adjust the thresholds to your system.
Models may inflate severity because they have insufficient context. They may not know what inputs an attacker actually controls, or they cannot see compensating controls. As an example of the former, a SQL injection is critical if triggered by an unauthenticated request but a non-issue if triggered by an admin-only config file. For the latter, upstream WAF or authentication that prevent exploits may not be visible from the source code alone.
The solution is to provide a threat model during triage that tells the model which types of vulnerabilities you do and don’t care about in your system. For example, clarifying that "we trust authenticated clients" can simplify or remove a whole class of criticals.
One team found the model is often overconfident unless grounded in something to verify, or has more context on whether something is expected as part of the threat model. Their fix was to give the triage agent the same threat model the discovery agent gets.
Try the triage skill. It does both verification and triage: multi-vote verification per finding, deduplication across runs, and re-ranking by derived exploitability. The output is a short, ranked, owned list instead of a raw dump.
6. Patching: Close the loop and improve context for the next cycle
Patching is where you close the loop and fix the vulnerabilities. It also helps to improve the threat model based on verified findings—updating trust boundaries or components that need more scrutiny—and feed past findings into the next scan’s context. Each cycle hardens the codebase and makes the next scan better informed.
Before patching, write a new test that fails with the existing code. Then, implement the fix and confirm the same test now passes without breaking anything else. (Yes, it’s test-driven development). If you don't add a test, the fix can silently regress and it can be hard to retroactively prove the bug was real.
One pentester found that their generated patches were inconsistent—some good, some bad—until the harness told the model to validate patches by re-running the proof of concept against the patched code. By giving the model feedback to iterate against, patch quality jumped, saving time on human review.
Models may narrowly address findings at a specific call site instead of the root cause. Simply prompting the model to identify and fix the root cause can be effective. Then, have the model look for variants at two levels: (1) same pattern, where there are other call sites or copies of the same buggy code elsewhere, and (2) same class, where a codebase with one SQL injection vulnerability tends to have more SQL injection vulnerabilities. Update the threat model with the validated findings and patches to close the loop.
Before you ship the patch, run an adversarial check. Have a new discovery agent probe the patch as an attacker to confirm the patch is comprehensive. Then, simplify the generated patch to address patches that are too invasive. Minimal patches are easier to review and less likely to introduce new bugs. Prompt for the smallest change that fixes the root cause—no refactoring, no drive-by cleanups, no reformatting.
One team on their most common patch failure: "The recommended patches tend to be as restrictive as possible, to the point that they would break connections with other services. It would address the issue, but break the dependencies that allow the service to work in the first place."
You can validate each patch against a ladder of checks, starting with the cheapest:
- Build. The patch compiles and the new tests pass.
- Try to reproduce. The original PoC should stop working. This catches ineffective patches.
- Check for regressions. The original test suite still passes. This catches broken or over-restrictive patches.
- Re-attack. A fresh discovery agent runs an adversarial check. This catches incomplete patches.
Finally, while the model can write the patch, a human still needs to own it. Generated patches can fail in predictable ways—fixing the symptom instead of the root cause, blocking legitimate input, or removing access to a dependent service. The goal is to validate each patch as much as possible so human review requires less effort. The goal is to help the dev team focus on nuances the model might be unaware of (e.g., incoming changes, code style) with minimal review and updates needed to patches.
Try the patch skill. It consumes the triage output and generates a candidate diff per finding, with an independent reviewer agent checking each one.
Getting started
Try running the loop yourself. Clone defending-code-reference-harness and run /quickstart in Claude Code. It walks you through an interactive workflow, from threat modeling to scanning to triage, on a demo target. The repo also includes an autonomous harness and a /customize skill to update the harness for your environment.
Then, run it on your own code. Pick a service or package. Bootstrap a threat model from the code and docs, and go through the interview. Invest in building a sandbox of your environment. Scan. Verify the findings with an independent agent. Triage based on your criteria and review everything rated high and above. Patch. Then re-scan periodically.
Your first scan will surface more findings than you’d expect. Most will require verification and triage. Budget for the pipeline after the scan before you budget for more scanning.
Some resources you might find helpful:
- Claude Security: Anthropic’s managed product for agentic vulnerability detection and patching.
defending-code-reference-harness: Companion repo with skills for interactive workflows and a demo harness for autonomous runs.claude-code-security-review action: Github action with Claude as a security reviewer on every pull request.- Threat Intelligence Enrichment Agent: Cookbook to build an agent that enriches indicators of compromise against threat intel feeds.
- Vulnerability Detection Agent: Cookbook to build an agent that builds a threat-model, scans for vulnerabilities, and triages findings into a structured report.
Moving forward
We believe it’s getting easier for models to find and exploit vulnerabilities in code. Thus, our work as defenders is to find and fix the vulnerabilities in our code before adversaries exploit them. Some teams have gone as far as connecting their harnesses to events, where a bug bounty report triggers an automated variant analysis, a security review triggers scanning and has candidate findings attached, or a verified vulnerability updates the static analysis tooling to prevent it in the future.
The work is critical and high stakes. But done right, it’s the start of a larger, more hopeful shift, where we’ll be able to find and fix vulnerabilities before attackers exploit them.
If you’d like to stay connected to our work on cybersecurity, please sign up to our mailing list, here.
Acknowledgements
Written by Eugene Yan and Henna Dattani, with contributions from Michael Molash, Abel Ribbink, Justin Young, Ben Morris, David Dworken, and Hasnain Lakhani. This work draws upon our experiences working with models for security at Anthropic and the valuable insights shared by our partners and customers, for which we’re deeply grateful.