0
0

Agentic Fuzzing 真正困难的不是生成输入,而是定义什么算 Bug

2026-07-16

当大模型开始参与测试生成、代码分析、调试和漏洞验证后,自动化系统能够生成的测试数量正在迅速增加。

传统 Fuzzer 可能每秒执行数千甚至数百万个输入;LLM 驱动的测试系统虽然吞吐量更低,却能阅读源码、理解 API、组合调用序列,并主动构造具有语义的边界条件。进一步的 Agentic Fuzzing 不再只是让模型生成几个输入,而是让 Agent 自主完成假设提出、测试编写、程序执行、结果分析和漏洞报告。

表面上看,生成能力越强,发现漏洞就应该越容易。

实际情况恰好相反:当测试对象从解析器、媒体库等传统 native 程序,转向 Agent Framework、工作流编排框架和复杂 Python 软件时,真正限制自动漏洞发现能力的往往不再是“能否生成输入”,而是一个更基础的问题:

系统观察到的异常,究竟是不是实现 Bug?

Crash、异常、测试失败、行为差异,甚至另一个大模型给出的“这是 Bug”判断,都不能单独回答这个问题。

可靠的 Agentic Fuzzing 必须同时约束输入是否合法、调用方式是否真实、目标代码是否可达、行为是否违反规范、问题是否稳定复现,以及维护者是否认可缺陷成立。否则,Agent 只会以更高速度生成更多看似合理、实际无法信任的安全结论。

一、为什么传统 Fuzzer 偏爱 Crash Oracle

Fuzzing 要解决两个相互关联但并不相同的问题:

  1. 如何产生能够探索新状态的输入;

  2. 如何判断一次执行是否暴露了缺陷。

前一个问题属于输入生成和搜索策略,后一个问题属于 Test Oracle。

在传统 native fuzzing 中,Crash 是一种非常有吸引力的自动化信号。段错误、非法内存访问、Sanitizer 报告、断言失败或进程异常退出,通常具有三个优点。

第一,结果相对确定。同一个输入在相同环境中重复执行,往往能够产生相同的崩溃。

第二,检测成本低。Fuzzer 不需要理解程序的业务语义,只需要监控退出码、信号或 Sanitizer 输出。

第三,容易大规模自动化。测试、检测、去重和最小化都可以进入无人值守流水线。

这也是为什么 AFL++、libFuzzer 和 OSS-Fuzz 等系统可以持续运行:它们不需要在每次执行后理解“程序本来应该做什么”。

但即使在传统 fuzzing 中,Crash 也只是强候选信号,而不是完整漏洞结论。崩溃可能来自错误的 harness、违反前置条件的调用、资源耗尽、测试环境问题或不可利用的防御性断言。Crash Oracle 能说明“程序在当前输入下出现了异常执行”,却不能自动证明“这里存在具有安全影响的产品缺陷”。

进入 Agent Framework 后,这种差距进一步扩大。

二、在 Agent Framework 中,异常本身不再可靠

LangChain、LlamaIndex、CrewAI 等 Agent Framework 大量使用 Python、Pydantic、动态配置、回调、异步接口和协议式抽象。测试一个 API,不仅需要参数类型正确,还需要对象初始化顺序、运行状态、依赖注入和调用协议全部成立。

在这种环境中,同一个 ValueErrorTypeErrorKeyError,可能来自完全不同的原因:

  • Fuzzer 构造了一个不符合 Pydantic Schema 的对象;

  • 测试代码遗漏了必需字段;

  • 调用者违反了文档规定的生命周期;

  • Mock 对象没有实现框架所要求的协议;

  • 框架没有正确处理一个本应合法的边界状态;

  • 内部状态被错误更新,随后在另一个位置触发异常。

从外部观察,这些情况都可能表现为普通 Python Exception。

这意味着“出现异常”并不能直接区分 API misuse 和 implementation bug。相反,如果系统把所有异常都报告为漏洞,误报会迅速淹没真正的问题;如果系统把常见 Python 异常全部过滤掉,又可能同时过滤真实缺陷。

LogicHunter 的实验对这一问题给出了直接证据。该论文将所有失败直接视为 Bug 时,虽然召回率达到 100%,但精确率只有 2.80%;简单启发式过滤后的精确率也只有 5.82%,平均需要人工检查 17.24 个失败用例才能确认一个真实 Bug。

因此,在复杂 Python 框架中,“异常发生了”只是一个观察结果,而不是 Oracle 的最终答案。

三、比异常更困难的是静默语义错误

异常至少会留下可监控的信号。更棘手的是另一类问题:程序正常返回,类型也正确,但语义已经发生错误。

例如,一个工具注册函数可能返回成功,却没有真正更新注册表;一个 Memory 组件可能正常保存消息,却破坏原有的时间顺序;一个 Graph Store 可能接受重复写入,却没有执行文档承诺的去重;一个异步接口可能返回正确的数据结构,但回调被调用了两次;一个权限过滤器可能返回合法对象,却混入了调用者无权访问的数据。

这些问题通常具有以下共同特征:

  • 没有 Crash;

  • 没有未捕获异常;

  • 返回类型符合声明;

  • 基础单元测试可能通过;

  • 代码覆盖率可能继续增长;

  • 错误只体现在状态变化、调用顺序、数据流或跨步骤关系中。

LogicHunter 报告的一个案例正是这种情况:LlamaIndex 的 SimpleGraphStore.upsert_triplet 在存储时使用列表,在判断重复项时却使用元组进行比较,导致相同关系可以被重复插入。整个过程不抛出异常,只有检查重复调用后图中元素数量是否保持不变,才能暴露问题。

这类 Bug 说明,面对 Agent Framework,仅检查“程序有没有崩溃”或“函数有没有返回”远远不够。Oracle 必须观察行为关系,例如:

  • 幂等性是否保持;

  • 输入字段是否被无意修改;

  • 状态转换是否符合预期;

  • 同步和异步接口是否一致;

  • 回调次数和顺序是否正确;

  • 工具调用是否满足权限边界;

  • 数据是否沿预期路径传播;

  • 多次执行是否产生稳定结果。

这已经不是传统意义上的 Crash Detection,而是在运行时检验一组局部语义不变量。

四、LogicHunter:先生成合法输入,再主动调查异常

LogicHunter 的核心价值不只是“使用 Agent 做 Fuzzing”,而是把输入生成和 Oracle 问题放在同一个闭环中处理。

截至 2026 年 7 月,其公开版本仍是 arXiv v1 预印本,因此论文中的结果应视为作者报告、尚未经过正式同行评审的实验结论。论文针对 LangChain、LlamaIndex 和 CrewAI 三个框架进行了评估。

1. Valid-by-construction inputs

传统 mutation-based fuzzing 会产生大量类型错误、缺失字段或违反协议的调用。这些输入通常在进入核心逻辑前就被 Pydantic 或框架校验层拒绝。

但只满足类型约束同样不够。始终使用默认值、空字符串和最简单对象,虽然“合法”,却很难触及有价值的语义边界。

LogicHunter 因此组合了两类信息:

  • 类型提示、Pydantic Schema 等形式约束;

  • 文档和真实开源项目中的 API 使用方式。

系统首先构造能够正常初始化和执行的 seed,再在 API 使用层进行变异,例如改变边界参数、重排控制流、组合状态依赖或构造语义极端但仍然合法的输入。

目标不是生成任意输入,而是生成:

在类型和协议层面合法,但在语义层面具有挑战性的输入。

这种 valid-by-construction 思路能够减少大量无效失败,但它仍然只解决了“测试能否进入有效状态空间”的问题,并没有自动解决“失败是不是 Bug”。

2. Behavioral probes

LogicHunter 为测试生成局部行为断言,即 behavioral probe。

Probe 可以检查返回类型一致性、字段保持、幂等性或边界行为。例如:

store.upsert_triplet(subject, relation, obj)
store.upsert_triplet(subject, relation, obj)

assert count_triplets(store) == 1

这个断言并不天然是真理。它是从文档、接口画像或代码语义中推导出来的待验证假设。

LogicHunter 明确区分了 probe 和最终 Oracle:probe 失败只代表发现了异常信号,不能直接作为 Bug 报告,之后仍需 Agentic Oracle 进行验证。

这种区分非常关键。很多 LLM 测试系统会把自己生成的断言失败直接计入“发现漏洞”,但如果断言本身误解了 API 语义,那么所谓漏洞只是模型对规范的错误猜测。

3. Agentic Oracle

Agentic Oracle 不只接收测试代码和错误日志,然后输出一个标签。它会主动执行调查:

  • 查询当前版本文档;

  • 搜索和阅读相关源码;

  • 检查内部运行状态;

  • 修改测试以区分不同假设;

  • 进行额外的定向执行;

  • 收集支持或否定 Bug 的证据;

  • 最终给出判断和理由。

这与普通的 LLM Judge 有本质差别。

普通 LLM Judge 进行的是静态分类:

test + output + context→bug / non-bug\text{test + output + context} \rightarrow \text{bug / non-bug}test + output + context→bug / non-bug

Agentic Oracle 执行的是主动诊断:

提出假设→检索证据→执行实验→排除替代解释→形成结论\text{提出假设} \rightarrow \text{检索证据} \rightarrow \text{执行实验} \rightarrow \text{排除替代解释} \rightarrow \text{形成结论}提出假设→检索证据→执行实验→排除替代解释→形成结论

论文报告称,LogicHunter 共发现 40 个此前未知的问题,其中包括 8 个意外异常类问题和 32 个静默失败问题;30 个获得开发者确认,26 个已经修复。

这一结果同时说明两件事。

第一,主动 Oracle 确实比单纯依赖 Crash 或静态 LLM 分类更适合 Agent Framework。

第二,即使经过 Agentic Oracle,40 个候选中仍只有 30 个获得开发者确认。剩余候选可能是真实但尚未处理的问题,也可能存在规范歧义、维护者判断差异或证据不足。Agentic Oracle 显著提高了可信度,但它仍然不是形式化证明,也不能替代最终确认。

五、为什么让另一个 LLM 判断“是不是 Bug”仍然不够

一个直观方案是:由第一个 LLM 生成测试,再让第二个 LLM 判断失败是否代表 Bug。

这个方案能够过滤一些低级错误,但仍存在五类根本限制。

1. 模型可能使用过期知识

Agent Framework 的 API 变化很快。模型记忆中的参数、默认行为或生命周期约束,可能已经不适用于当前版本。

即使向模型提供文档,也不能保证文档与具体 commit 的实现完全一致。

2. 模型可能接受测试中的错误前提

测试代码可能假设一个函数具有幂等性,但文档并未作出这一保证;也可能假设调用顺序可以任意调整,而框架实际要求先初始化特定组件。

LLM Judge 若没有独立验证,很容易在测试自身错误的前提下继续推理。

3. 日志不足以恢复内部状态

很多语义错误只能通过检查对象字段、缓存内容、事件序列、调用参数或源码分支才能确认。仅凭最终异常栈或输出值无法完成归因。

4. 生成模型和判断模型可能共享相同偏差

即使使用两个模型,它们也可能对 API 产生相同误解。模型数量增加并不等于证据独立性增加。

5. 语言判断不能替代可执行证据

“这段代码看起来违反预期”只能形成假设。可靠报告还需要证明:

  • 输入满足公开或隐含前置条件;

  • 异常来自目标实现而非测试 harness;

  • 问题在固定环境中可重复;

  • 对照输入不会产生同样结果;

  • 源码路径能够解释观察到的行为。

LogicHunter 的实验也显示,被动 LLM Judge 的精确率仍然有限。论文报告的 GPT-5.2 被动 Judge 精确率约为 29%,而 Agentic Oracle 报告的精确率为 91.17%、召回率为 72.14%,平均人工检查 1.10 个失败用例可确认一个真实 Bug。

需要注意,这一结果来自论文作者构建的评估集和实验设置,不能直接外推到所有软件项目。但它至少表明:模型能力更强,并不能消除主动取证和运行时验证的必要性。

六、一个更可靠的 Oracle 分层

Agentic Fuzzing 不应该把 Oracle 设计成一个简单的布尔函数:

is_bug(test_result) -> true / false

更合理的设计是一条分层证据链。只有候选逐层通过,结论强度才逐渐提高。

第一层:语法与类型合法

检查生成代码能否解析、导入和执行,参数是否符合类型提示、Schema 和显式 Validator。

这一层排除明显的生成错误,例如:

  • 不存在的参数;

  • 错误字段类型;

  • 缺少必填属性;

  • 无法实例化的抽象类;

  • LLM 幻觉产生的 API。

通过这一层只说明测试“形式上可运行”,不说明调用方式合理。

第二层:API 协议合法

检查测试是否遵循框架要求的对象生命周期、初始化顺序、异步协议、上下文管理和依赖约束。

例如:

  • 是否应先调用 initialize()

  • 是否必须在 event loop 中使用;

  • Mock 是否完整实现目标协议;

  • 当前方法是否允许用户直接调用;

  • 测试是否绕过了正常入口。

这一层决定候选是“有效 API 使用”,还是高级形式的 misuse。

第三层:真实入口可达

即使某个内部方法可以被人工调用,也不代表真实应用能够到达该状态。

需要证明:

  • 状态可通过公开 API 构造;

  • 参数组合出现在合理使用场景中;

  • 不依赖任意篡改私有字段;

  • 不需要违反产品部署假设;

  • 上游组件确实能够产生相应输入。

真实入口可达性是区分测试技巧和实际缺陷的重要边界。对于安全漏洞,还应进一步说明攻击者是否能够控制相关输入,以及需要哪些权限和前置条件。

第四层:行为与规范冲突

这一层要求提供行为异常所违反的依据。

依据可以来自:

  • 明确的 API 文档;

  • 类型或 Schema 中表达的不变量;

  • 官方示例;

  • 测试套件中的预期行为;

  • 同步与异步实现之间的差分关系;

  • 幂等性、单调性或状态保持等可解释属性;

  • 源码中的注释、检查和控制流意图。

规范来源可能相互冲突。例如文档与实现不一致时,不能自动断言实现一定错误,也可能是文档 Bug。报告必须保留这种不确定性,并说明当前证据支持哪一种解释。

第五层:最小复现稳定

候选应当被压缩为尽可能小的 PoC,并在固定版本和环境中重复执行。

至少需要记录:

  • 目标 commit 或 release;

  • Python 和依赖版本;

  • 环境配置;

  • 完整调用序列;

  • 预期行为;

  • 实际行为;

  • 重复次数和成功复现次数;

  • 对照实验;

  • 是否存在并发或外部服务依赖。

一次偶然失败不能支撑高置信度结论。对于非确定性 Agent 工作流,还应报告复现概率,而不是只展示一次成功日志。

第六层:人工或上游确认

人工审核需要检查前面各层证据是否完整。上游维护者确认、Issue 接受、补丁合并或安全公告可以进一步提高结论可信度。

但这些信号也不能简单等同:

  • 提交 Issue 不等于确认;

  • 标记为 Bug 不等于安全漏洞;

  • 合并修复不等于认可全部攻击叙事;

  • 获得 CVE 不等于证明可利用性;

  • 未获得回复也不等于候选为误报。

因此,最终结果应按证据等级报告,而不是把所有候选都称为“发现漏洞”。

一种可执行的分类方式是:

Generated Case
    ↓
Valid Test
    ↓
Reachable Anomaly
    ↓
Specification Violation
    ↓
Stable Reproducer
    ↓
Human-Validated Bug
    ↓
Developer-Confirmed Bug
    ↓
Security-Relevant Vulnerability

每向下一层移动,都需要新的证据,不能依赖模型置信度自动升级。

七、Agentic Fuzzing 论文应该报告什么

生成 10 万个测试、触发 5000 个异常,或者由 LLM 标记出 300 个“疑似漏洞”,都不能单独证明系统有效。

一篇可信的 Agentic Fuzzing 论文至少应报告以下指标。

1. 生成规模

包括生成测试数、实际执行数、执行成功数和有效输入比例。

这反映生成器是否真正理解目标接口,而不是仅仅制造大量不可运行代码。

2. 候选异常数量

应区分:

  • Crash;

  • 未捕获异常;

  • Probe 失败;

  • 差分不一致;

  • 状态不变量违反;

  • 模型判断异常。

不同信号的证据强度不同,不能合并为一个笼统的“Bug candidates”。

3. 去重后的独立候选

需要说明去重依据是异常栈、根因、受影响 API、补丁位置还是开发者可见行为。

多个输入触发同一个根因,不能被重复计数为多个漏洞。

4. 验证后真实 Bug

至少分别报告:

  • 人工确认;

  • 上游确认;

  • 已修复;

  • 重复问题;

  • 预期行为;

  • 无法复现;

  • 尚未判定。

“提交了多少 Issue”不能代替“确认了多少 Bug”。

5. Precision、False Positive Rate 和 Recall

Precision 决定人工审核负担。对于需要安全研究人员逐个检查的系统,即使召回率很高,低精确率也可能使系统无法使用。

Recall 更难评估,因为真实未知 Bug 集合通常不存在。论文可以使用历史已修复 Bug、mutation benchmark、人工构建缺陷或后验开发者确认,但必须明确每种基准的局限。

6. 无法判定比例

复杂 Agent Framework 中会有大量规范不清、依赖外部服务或需要完整应用环境的候选。

将这些样本强制标记为 Bug 或 non-Bug 会产生虚假确定性。更合理的分类应包含 unknowninsufficient evidence

7. 单位 Bug 验证成本

至少应统计:

  • LLM Token 或 API 成本;

  • Agent 执行时间;

  • 机器运行时间;

  • 人工审核时间;

  • 每个确认 Bug 所需检查的候选数;

  • 最小化和复现成本。

PBFuzz 等 Agentic Directed Fuzzing 工作已经开始报告单位漏洞 API 成本,这比只报告测试数量更接近系统的实际部署价值。

8. 从候选到最终结论的完整漏斗

论文应给出类似下面的结果链:

100,000 generated tests
18,000 executable tests
7,500 protocol-valid tests
1,200 unique anomalies
160 oracle-positive candidates
48 stable reproducers
31 human-confirmed bugs
24 developer-confirmed bugs
8 security-relevant vulnerabilities

这个漏斗比“发现 160 个潜在漏洞”更真实,也能暴露系统的主要损耗究竟发生在输入生成、运行验证还是人工确认阶段。

八、Oracle 本身也需要被评估

Agentic Oracle 不是评估体系之外的裁判,而是系统中另一个可能出错的组件。

因此,需要单独评估:

  • Oracle Precision;

  • Oracle Recall;

  • 不同运行之间的一致性;

  • 对文档版本变化的敏感性;

  • 去掉源码访问后的性能;

  • 去掉运行时检查后的性能;

  • 不同基础模型下的稳定性;

  • 对 hard negative cases 的误报率;

  • 是否会被测试代码或目标仓库中的文本诱导。

特别是在 Agent 环境中,Oracle 读取的文档、源码注释、Issue 和示例本身可能错误、过期,甚至具有对抗性。一个依赖仓库上下文进行判断的 Agentic Oracle,也可能遭遇 prompt injection 或 context poisoning。

因此,更成熟的设计应区分:

  • 非可信输入:测试内容、异常信息、仓库文档和注释;

  • 半可信依据:当前版本官方文档和已有测试;

  • 强证据:可重复运行结果、源码控制流和最小对照实验;

  • 外部确认:维护者回复、补丁和安全公告。

Oracle 越依赖自然语言推理,就越需要独立的可执行证据进行约束。

九、自动化生成越强,验证层越不能被省略

Agentic Fuzzing 的进步,使测试系统从随机扰动器逐渐变成了自主研究者。

Agent 可以阅读历史漏洞,理解根因,在大型代码库中寻找变体,编写 PoC,并通过调试反馈持续修正假设。AFuzz 已经展示了这种方法在复杂 JavaScript 引擎逻辑 Bug 上的潜力:它不再局限于围绕原始 PoC 进行局部变异,而是尝试理解历史 Bug 的高层根因,再寻找具有不同触发路径的同类问题。

但推理能力越强,系统就越容易生成听起来完整的漏洞故事:

  • 它可以解释为什么代码看起来危险;

  • 可以构造一个能够失败的测试;

  • 可以生成攻击场景;

  • 可以撰写具有说服力的漏洞报告。

这些能力都不能自动保证结论正确。

测试生成解决的是探索问题,Oracle 解决的是认识论问题:我们凭什么相信这个现象构成 Bug?

在 Agent Framework 和复杂软件中,可靠答案通常来自多种证据的组合:

  • 类型和 Schema 证明输入在形式上合法;

  • 真实调用方式证明场景不是人为误用;

  • 运行状态证明异常确实发生;

  • 源码语义解释异常产生的根因;

  • Behavioral Probe 暴露静默错误;

  • 最小复现和对照实验排除偶然因素;

  • 人工或上游确认完成最终归因。

其中任何单一信号都可能出错。真正可靠的 Oracle 不是某个更强的分类模型,而是一条能够被检查、重复和反驳的证据链。

结语

Agentic Fuzzing 真正困难的不是让模型生成更多输入,而是建立一个足够可靠的判断体系,区分:

  • 无效测试与合法边界输入;

  • API misuse 与实现缺陷;

  • 可疑行为与规范冲突;

  • 一次性失败与稳定 Bug;

  • 普通软件缺陷与安全漏洞;

  • 模型判断与可验证事实。

Crash 很便宜,但覆盖不了静默语义错误;异常很明显,但无法区分误用和内部缺陷;LLM 能够解释,但解释不等于证据;Agent 可以主动调查,但仍需要确定性检查和人工确认。

自动化生成能力越强,Oracle 质量越重要。

没有可靠的验证层,Agentic Fuzzing 不会自动带来更多可信漏洞,只会更快地产生更多需要人工重新调查的结论。真正有价值的系统,不是声称发现最多 Bug 的系统,而是能够清楚说明每个结论经过了哪些验证、还存在哪些不确定性,以及证据足以支持到什么程度的系统。

Comments