使用 Claude 和基于属性的测试发现漏洞

Finding bugs with Claude and property-based testing

📅2026-06-18👤AnthropicAnthropic Blog
✍️翻译:DeepSeek

前沿红队

Frontier Red Team

使用 Claude 和基于属性的测试发现 Python 生态系统中的漏洞

Finding bugs across the Python ecosystem with Claude and property-based testing

2026 年 1 月 14 日

Muhammad Maaz¹,², Liam DeVoe³, Zac Hatfield-Dodds², Nicholas Carlini²¹MATS, ²Anthropic, ³Northeastern University

我们开发了一个能够高效识别大型软件项目中漏洞的智能体。为此,我们的智能体推断代码应满足的通用属性,然后通过应用基于属性的测试(property-based testing)——一种类似于模糊测试(fuzz testing)的技术——我们能够在 NumPy、SciPy 和 Pandas 等顶级 Python 包中发现漏洞。经过大量人工验证后,我们正在向开发者报告这些漏洞,其中多个已被修复。

更多信息,请阅读完整论文、查看 GitHub 仓库,或浏览我们在网站上发现的漏洞。

引言

Introduction

确保程序无漏洞是软件工程中最具挑战性的方面之一。尽管开发者付出了最大努力,漏洞仍常常潜伏其中。最常见的代码测试方法是基于示例的测试(example-based tests):开发者编写一个具体的示例用例,然后验证实际输出是否与预期输出匹配。例如,开发者可能会验证对列表 [2, 10, 5, 4] 调用的排序函数会返回输出 [2, 4, 5, 10]。然而,用这样的测试穷尽地覆盖程序是极具挑战性的:漏洞常常存在于开发者未测试的边缘情况中。毕竟,如果开发者没有想到测试某个边缘情况,那么该情况很可能在实现中也未被考虑!

相比之下,基于属性的测试(property-based testing) 是一种软件测试范式,旨在测试代码的某个通用属性是否对所有(或大多数)输入成立。开发者指定程序的一个属性或不变量(invariant)——例如,JSON 反序列化是序列化的逆操作——以及该属性接受的输入类型描述(例如,任何可 JSON 序列化的对象)。然后,基于属性的测试框架通过使用类似于模糊测试的技术生成有效输入作为测试用例,自动搜索该属性的反例。由于开发者指定的是通用输入域,而非单个测试用例,基于属性的测试使开发者无需思考每一个边缘情况,从而能够在更高的抽象层次上工作。

在我们于 2025 年 NeurIPS 深度学习与代码 研讨会上展示的论文中(该论文是 MATS 项目的研究成果),我们开发了一个 AI 智能体,能够为现有代码自主编写基于属性的测试。我们指导智能体通过读取类型注解、文档字符串、函数名和注释来发现属性。然后,智能体使用 Hypothesis 编写相应的基于属性的测试。

在这项工作中,我们专注于识别一般性漏洞,而不仅仅是安全漏洞。许多导致安全漏洞的逻辑错误类别都适合使用基于属性的测试。例如,在最近的一篇博客文章中,我们专注于识别智能合约中的漏洞;几乎所有那些漏洞都是逻辑错误的结果。未来,我们设想可能可以应用本文所述的技术,在部署前主动识别漏洞。

我们使用智能体在 NumPy、SciPy 和 Pandas 等流行的开源 Python 仓库中发现了数百个潜在漏洞。为了负责任地披露这些漏洞,并确保不给维护者带来不必要的负担,我们仔细审查了每个漏洞。

我们使用的审查过程比我们通常为自己的代码审查所实施的过程更为繁琐,但我们强烈倾向于减少误报数量。我们的流程如下:首先,我们只选择最高优先级的漏洞进行审查。我们将这些潜在漏洞发送给三位人类专家进行审查(平均每个漏洞审查一小时)。然后,我们丢弃任何三位人工审查者中对其有效性不确定的漏洞。最后,我们(本博客的作者)手动审查了每个候选漏洞。只有当我们对其正确性也有信心时,我们才手动向仓库维护者提交问题报告。我们已经提交了其中几个漏洞报告,并正在提交更多。

我们已公开所有数据,包括尚未验证的漏洞,甚至包括我们确定为无效的漏洞,以便维护者可以在本网站上查看。在接下来的几周内,我们打算提交许多剩余的漏洞(经过额外验证后),并将我们的项目扩展到更多的 PyPI 项目。

# 基于示例的测试
def test_sort():
    assert my_sort([1,3,2]) == [1,2,3]
    assert my_sort([1,0,-5]) == [-5,0,1]

# 基于属性的测试(使用 Hypothesis)
from hypothesis import given, strategies as st

@given(st.lists(st.integers()))
def test_sort(lst):
    result = my_sort(lst)
    for i in range(len(result)-1):
        assert result[i] <= result[i+1]

图 1. 测试代码的两种方式。基于示例的单元测试验证特定的手动指定的输入。相比之下,基于属性的测试指定一个通用属性(例如,排序列表的定义),并依赖框架自动构造可能使该属性失效的输入。

我们的基于属性测试智能体

Our Property-Based Testing Agent

我们的基于属性测试智能体是作为一个自定义的 Claude Code 命令构建的。该智能体接受一个参数,指向特定的目标,可以是单个 Python 文件(例如,normalizers.py)、一个模块(例如,numpy、scipy.signal)或一个函数(例如,requests.get、json.loads)。然后,智能体按照以下过程识别潜在漏洞:

  • 阅读并理解目标:通过阅读代码、拉取相关文档,并探索其与代码库其余部分的关系。
  • 提出基于这些发现的属性
  • 编写相应的基于属性的测试(使用 Hypothesis)。
  • 运行测试并反思:如果测试失败,是测试真正发现了漏洞,还是测试需要调整?如果测试成功,该测试是否测试了有价值的东西,还是仅仅是琐碎的?
  • 如果智能体确信发现了真正的漏洞,它会编写出格式化的漏洞报告。

图 2. 智能体的工作流程。

为了帮助智能体进行长距离的多步推理,我们指导它使用待办事项列表来跟踪进度。

设计智能体的一个优先事项是减少误报数量——根据我们的个人经验,有用的开发者工具应尽量减少向开发者呈现的错误报告数量。自我反思循环有助于减少误报数量,同样,将任何属性建立在目标的显式用法和文档基础上也有帮助。例如,在一次智能体运行中,智能体编写了一个首先通过的属性。然而,经过自我反思,智能体意识到它将整个测试包裹在了一个 try-catch 块中。移除该块后,测试失败,智能体发现了一个漏洞。我们还观察到,与 Sonnet 4 相比,Opus 4.1 和 Sonnet 4.5 在自我反思方面有显著改进。

示例运行

Example run

为了演示智能体如何测试目标,我们展示了一个转述的对话记录,其中 Claude 识别了 numpy.random.wald 实现中的一个漏洞。智能体首先调查该函数、其签名、其文档字符串,甚至现有的测试:

💡 译者注:此处原文省略了具体的对话记录,以保持文章结构清晰。

请注意,修复并不正确;当我们修复这个漏洞时,我们将错误的根源追溯到一次数值不稳定的计算;请在此处查看我们合并的修复。

在真实的 PyPI 包中搜索漏洞

Searching for bugs in real PyPI packages

为了测试我们的智能体在现实世界中的能力,我们精心挑选了超过 100 个多样化的流行 Python 包。这些库涵盖了从数值计算到解析再到数据库的各个领域。我们对每个包调用了我们的智能体,并收集了所有生成的漏洞报告。

评估智能体

Evaluating the agent

在评估的第一阶段(涵盖在我们的论文中),我们使用 Claude Opus 4.1 在每个包上运行智能体,并收集所有生成的漏洞报告。为了评估这些漏洞报告,我们确定了两个标准:第一,"这是一个有效的漏洞吗?",第二,"这既是一个有效的漏洞,又是我们合理向库维护者报告的内容吗?" 第二个标准比第一个更严格,因为,例如,一个漏洞可能是有效的,但过于微小而不值得提交报告。

在 984 份漏洞报告中,我们手动选择了 50 份进行审查。我们发现其中 56% 是有效漏洞,32% 是有效且我们会报告的漏洞。

基于这次手动审查,我们制定了一个评分标准,将漏洞按 15 分制评分,旨在筛选出最可能有效且值得开发者修复的漏洞。我们使用 Opus 4.1 根据此评分标准对所有漏洞报告进行评分。我们发现这个排名步骤相当有效:在得分最高的漏洞报告中,86% 是有效的,81% 既有效又可报告。

在评估的第二阶段,我们在 10 个重要包的子集上使用 Sonnet 4.5 运行智能体,对每个包运行多次。我们还开发了一个使用 Sonnet 4.5 的评估智能体,用于读取代码和漏洞报告,以检查漏洞的正确性和严重性,这比第一阶段的评分标准更为复杂。最后,我们付费聘请了 3 位人类专家审查员来评估高严重性漏洞的正确性。

要阅读我们的智能体发现的所有漏洞报告,请访问 https://mmaaz-git.github.io/agentic-pbt-site/。

维护者验证

Maintainer validation

评估任何发现代码漏洞的工具的有效性都很困难。虽然我们尽力验证漏洞报告的正确性,但包维护者是最终的真理仲裁者。为了验证我们的智能体能够发现维护者认为有效且值得修复的漏洞,我们选择了五个特别有趣的漏洞,并手动将这些漏洞连同建议的补丁一起报告给了各自的 GitHub 仓库。在接下来的几周内,我们打算在验证后继续报告更多漏洞。

numpy

numpy.random.wald 有时返回负数,这是一个漏洞,因为 Wald 分布的样本应只返回正数。这是上面示例运行中演示的漏洞。Claude 知道这是 Wald 分布的一个属性,并编写了一个直接的基于属性的测试来检查所有生成的样本是否为正。我们将错误追溯到代码中发生的一次灾难性抵消(catastrophic cancellation),并在提交拉取请求时开发了一个数值上更稳定的公式。如 NumPy 维护者在拉取请求中所示,我们的重新公式化相比之前的算法,相对误差降低了近十个数量级。

补丁已合并:https://github.com/numpy/numpy/pull/29609

aws-lambda-powertools

slice_dictionary() 由于未递增迭代器而重复返回第一个分块。我们的智能体通过识别出切片然后重建字典应返回原始字典来捕获此漏洞。

补丁已合并:https://github.com/aws-powertools/powertools-lambda-python/pull/7246

cloudformation-cli-java-plugin

item_hash() 对所有列表产生相同的 hash(None) 值,这是由于使用了原地 .sort() 方法,该方法返回 None。智能体通过测试不同输入的哈希值应不同来捕获此漏洞。

补丁已提交:https://github.com/aws-cloudformation/cloudformation-cli/pull/1106

tokenizers

EncodingVisualizer.calculate_label_colors() 缺少一个右括号,返回无效的 HSL CSS。我们的智能体通过测试输出应匹配 HSL 颜色代码的正则表达式来识别此漏洞。

补丁已合并:https://github.com/huggingface/tokenizers/pull/1853

python-dateutil

easter() 在使用儒略历的某些年份返回非星期日日期。维护者确认该行为是由于不同的历法系统而有意为之,并承认其语义较为微妙。

问题无效:https://github.com/dateutil/dateutil/issues/1437

python-dateutil 的报告显示了智能体的一个重要局限性:从具有微妙或复杂语义的代码中推导属性仍然很困难。如果代码做出了隐式假设,只有库维护者才能决定要测试的正确属性是什么。

结论

Conclusion

随着语言模型的不断改进,我们认为智能体驱动的基于属性的测试可能成为人工编写测试的越来越有价值的补充。基于属性的测试提供的高级语义保证使其在开发过程中与人工测试自然契合。我们发现,LLM 特别擅长从上下文(函数名、文档字符串、其他函数如何调用它等)中识别出给定代码块应该成立的属性。这使得 LLM 能够有效地编写高质量的基于属性的测试。

展望未来,我们认为将 LLM 应用于测试和漏洞发现是一个重要的研究方向。特别是随着 LLM 在利用漏洞方面的能力不断提升,我们必须领先于使用 LLM 进行漏洞利用的攻击者。

虽然在这项工作中我们没有专注于自动生成补丁,但这显然是未来工作的一个方向。如果能够(近乎)完全地指定一段代码的正确性属性,那么纠正漏洞将变得容易得多,我们相信在不久的将来,LLM 将能够有效地提出值得维护者考虑的高质量补丁。