GitHub Security Lab 推出用于 AI 驱动 fuzzing 的 Taskflow Agent 流水线
AI-powered fuzzing with the GitHub Security Lab Taskflow Agent
GitHub Security Lab 介绍了 Fuzzing Taskflow,这是一个面向 C/C++ 项目的自主 fuzzing 流水线,基于 GitHub Security Lab Taskflow Agent 构建。
文章拆解了 LLM agent 接管 fuzzing 覆盖改进和崩溃分诊的流程,对安全自动化工作流有参考价值。
如果你是 fuzzing 新手,并且想先学习基础知识,请查看我们在 gh.io/fuzzing101 上的 Fuzzing 101 课程。
持续 fuzzing 并不是能解决你所有问题的魔法方案。即使是已经加入 OSS-Fuzz 多年的项目,仍然可能隐藏着关键漏洞,而原因几乎总是一样的:需要有人关注覆盖率,为那些无人触及的代码编写新的 harness,并对另一端产生的崩溃进行分诊。换句话说,fuzzing 仍然需要人在环路中参与。
所以我一直问自己的自然问题是:这些人工工作中,究竟有多少可以真正交给一个 LLM agent?
这正是促使我构建 Fuzzing Taskflow 的原因,它是一个面向 C/C++ 项目的自主 fuzzing 流水线。你只需要把它指向一个 GitHub 仓库,剩下的它会全部完成:识别合适的入口点、分析构建系统、编写 harness、运行 AFL++、读取覆盖率报告、改进 harness、分诊每一次崩溃,并为每个唯一漏洞编写漏洞报告,全程无需人工看护。
Fuzzing Taskflow 构建在 GitHub Security Lab Taskflow Agent 之上,这是我们用于编写由 LLM 驱动的安全自动化的框架,因此该流水线被表达为一组由 agent 端到端运行的 taskflow。
在这篇文章中,我将带你了解它的工作方式以及背后的设计决策。开始吧!
如何运行它
运行它最简单的方式,就是前往 https://github.com/GitHubSecurityLab/seclab-taskflows-fuzzing 并启动一个 codespace。
然后,像这样运行脚本:
./scripts/fuzzing/run_fuzzing.sh PROJECT例如:
./scripts/fuzzing/run_fuzzing.sh tukaani-project/xz就这样。参数只是一个 GitHub owner/repo slug。然后,agent 会自行处理所有前期步骤:
- 安装 AFL 等软件
- 克隆仓库
- 识别代码中最相关的函数
- 为这些函数创建 fuzz 目标
如果你只是想在投入长期活动之前做一个快速冒烟测试,可以把它指向一个小项目:
./scripts/fuzzing/run_fuzzing.sh DaveGamble/cJSON运行之前先提醒一句:这个 taskflow 会直接在主机上运行 afl-fuzz、clang 以及 由 LLM 选择的任意构建命令,中间没有容器隔离。一个遭受提示注入的 agent 理论上可以执行你的用户能做的任何事。因此请只在一次性环境中运行它(例如 Codespace 或临时 VM),并且不要使用提升的权限。

模型选择
一些前沿模型会对其输出施加安全护栏。对于 fuzzing 任务流,我们默认使用 Claude Sonnet 5,因为它通过了我们所有内部测试且没有问题。你可以通过修改以下文件来选择不同的模型:src/seclab_taskflows_fuzzing/configs/model_config.yaml。
一分钟了解架构
在进入有趣的部分之前,先了解各个组件如何组合会有帮助。它有三层:
- 一个shell 驱动脚本(run_fuzzing.sh),用于把流水线各阶段串联起来。
- 一组 taskflow YAML,每个阶段一个,本质上是提示词,用来告诉 LLM agent 在每一步该做什么。
- 一组 MCP 工具,agent 会调用它们来实际完成工作:运行 AFL、编译 harness、存储崩溃、读取覆盖率报告,等等。
我最在意的设计原则是清晰的职责分离:LLM agent 负责决策,MCP tools 负责执行。agent 决定要模糊测试什么、编写什么 harness,以及下一步追踪哪个覆盖率缺口。tools 只暴露诸如 run_afl_for 或 compile_harness 这样的基本原语。agent 从不直接调用 AFL 或 clang;它用这些构建块组合出整条流水线。所有状态都保存在一个 SQLite 数据库(fuzz_context.db)中,因此各个阶段从不在内存中相互传递数据,只通过数据库传递。
一个小但重要的细节:每个 harness 都会构建两次。AFL 的边插桩很适合引导 fuzzer,但对人类可读的覆盖率报告没有用。所以每个 harness 都会生成一个 .afl 二进制文件(用 afl-clang-lto -fsanitize=address,undefined 构建)和一个 .cov 二进制文件(用 clang -fprofile-instr-generate -fcoverage-mapping 构建)。.afl 二进制文件负责模糊测试;.cov 二进制文件随后重放 AFL 的队列,以生成真实的源码行和分支覆盖率。
覆盖率反馈循环
这是整条流水线的核心,也是最直接自动化我在开头描述的手动工作流的部分。
如果你曾经尝试过手动提升模糊测试覆盖率,就会知道这是一个如下所示的迭代过程:

“检查覆盖率”这一步过去由我完成:手动阅读 LCOV 报告,寻找未覆盖的分支。“提升覆盖率”这一步也由我完成,这一次是编写新的 harness 或构造新的输入。Fuzzing Taskflow 把这两个步骤都交给了 agent。
在每次迭代中,对于每个 harness,agent 都会在给定时间预算内运行 AFL,将队列在 .cov 二进制文件上重放以获得真实覆盖率报告,然后读取未覆盖分支列表。根据发现的情况,它会从少数几种动作中选择一种:
- 添加一个新的 seed,专门构造来触达未覆盖的分支
- 编辑 harness 源码以调用一个额外的 API
- 用 guard 正在比较的魔法常量自动丰富 AFL 字典
- 如果这个缺口是冷门错误路径,或是不值得追踪的供应商代码,就直接跳过
时间预算每轮迭代都会翻倍:
30s → 60s → 120s → 240s → 480s → 960s(≈ 32 min/target)
思路是在早期投入廉价而短的轮次(那时有大量容易拿到的覆盖率),在后期投入更长轮次(那时 fuzzer 需要更多时间突破困难的 guard)。
就像在我的手动工作流中一样,我需要回答一个问题:什么时候停止? 在这里,循环使用平台期检测:一旦连续两次迭代的增益都低于可配置阈值(默认是 1% 的绝对行覆盖率),循环就判定已经进入收益递减阶段并继续前进。这可以避免 agent 为了挤出最后零点几个百分点而消耗数小时算力。
结构感知模糊测试
AFL 默认的字节级变异器(位翻转、算术变异、块拼接)在二进制格式上表现很好,但在结构化的文本输入上并不擅长。经典解决方案是为每种格式手写自定义变异器,这是一项乏味的工作。这一次我希望流水线替我完成这项工作,所以它内置了四种互补机制来生成结构感知输入。
1. 按格式定制的字典和自定义变异器。对于输入格式可识别的目标(JSON、XML、regex、PNG、带长度前缀的二进制 TLV),该任务流随附预构建的 AFL 字典和 LLVMFuzzerCustomMutator C 文件。JSON 变异器会进行 token 拼接和平衡括号复制;XML 变异器了解标签、实体和 billion-laughs token;regex 变异器携带真实的 ReDoS 模式。每个变异器都会把一半变异委托回 AFL 的默认字节变异器,因此我们保留了引擎的随机化,而不是与之对抗。
2. 源码级字典。 对于流水线无法识别的格式,它会通过扫描目标自身的 .c/.h 文件即时生成自定义变异器。它提取字符串字面量和 32-bit 数值常量(来自 #define、case 和 enum),过滤掉噪声,并将它们用作拼接 token。直觉很简单:解析器检查的最有趣的魔数,通常会写在它自己的源码某处。
3. 动态生成、由覆盖率驱动增强的 AFL 字典。同一组源代码 token 也会在第 1 次迭代前作为 AFL 经典字典输出(数值常量同时采用两种端序,因此 fuzzer 无论主机字节序如何,都能满足对 4 字节 magic 的 memcmp)。然后,在每个覆盖率步骤之后,流水线会查看未覆盖行附近的守卫条件(strncmp、memcmp、case 0xN、== ‘X’),并追加它发现的任何新 token。这个字典会实实在在地朝着 fuzzer 尚未能到达的代码增长。
4. 语料库拼接算子。智能变异器还可以从语料库目录加载文件,并把它们的随机子区域拼接到输入中,这是一种重组式算子,而 AFL 自带的 havoc 做得并不好。
演进中的语料库
悄悄扼杀模糊测试效率的一件事,就是丢弃进展。如果每次运行都从原始种子开始,你就要一遍又一遍地重新付出发现相同路径的成本。
为避免这种情况,每个 harness 都会获得一个稳定的语料库目录,可跨迭代、跨整个 campaign 保留:
<workspace>/corpus/harness_<id>/在每次迭代结束时,AFL 的队列会合并到这个目录中,并通过 afl-cmin 处理以限制其大小。效果是,昨天发现的有趣输入会带入今天的运行,而上周 campaign 中找到的输入也会带入这次运行。如果你停止并重启一个 campaign,不会丢失任何内容。
分诊和漏洞报告
发现崩溃只完成了一半工作。任何做过根因分析的人都知道,分诊往往是整个过程中最繁琐的部分。这正是该 agent 另一个大显身手的地方。
模糊测试循环结束后,会自动运行三个阶段。首先,每个崩溃都会用 afl-tmin 最小化,在 ASan 下重放以捕获堆栈跟踪,并通过 stack-top hash 去重(取顶部规范化帧,去除模板、内联命名空间和 LTO 后缀,使语义相同的崩溃合并到一起)。其次,会用当前二进制文件重放此前已知的崩溃,以查看上游修复是否已经解决它们。第三,agent 会读取 harness 源码和发生崩溃的函数,从公共 API 沿调用链向回追溯,并为每个崩溃编写一份 markdown 报告。
每份报告都会给出以下判定之一:
- 漏洞
- library_hardening
- harness_bug
- OOM
- timeout
- assertion_failure
- duplicate
真正的漏洞(可通过公共 API 触达并利用)与单纯的 harness_bug(bug 出在我们自己的 harness 中,而不是库中)之间的区别,正是过去需要我坐下来手动跟踪代码才能作出判断的那类问题。每份报告都包含带有 file:line 引用的根因分析、可达性论证、可利用性评估、一个以统一 diff 形式给出的建议修复以及回归测试草案。
补充说明一下:建议补丁被标记为“需要审核”是有原因的。该代理的分析受限于模型对目标代码的理解,它确实会出错。请把这些判定视为为人工审查充分准备的起点,而不是最终结果。
实时仪表板
运行一场自主活动却看不到它在做什么会让人不安,因此该流水线会把所有内容发布到一个实时 HTML 仪表板。你启动活动后,它会立即在后台自动启动,端口为 8765。在 Codespace 中,该端口会自动转发,因此你可以在任何浏览器中打开它,并在仪表板上实时查看活动进展。

该页面显示的内容包括:
- 每个 harness 的“运行中”脉冲
- 带有内联迷你图的覆盖率趋势表
- 崩溃热力图
- 迭代时间线
结论
我启动这个项目的动机,源于任何安全研究人员都非常清楚的限制:模糊测试是有效的,但如果没有人工关注就无法扩展,而这种人工关注正是瓶颈。Fuzzing Taskflow 是我试图把这一瓶颈向后推的尝试:把重复性部分(编写 harness、读取覆盖率、追踪缺口、分诊崩溃)交给 LLM 代理,同时在代理的判断与真正执行工作的工具之间保持清晰分离。
如果你是 C/C++ 项目的维护者,请试用一下。如果你的项目以前从未进行过模糊测试,那么这个工具将帮助你快速入门。或者,如果你的项目以前已经进行过模糊测试,那么这个工具可能会通过提高模糊测试覆盖率来帮助发现新的 bug。
源代码是开源的,所以如果你遇到任何 bug,请创建一个 issue。也欢迎贡献!
文章 借助 GitHub Security Lab Taskflow Agent 进行 AI 驱动的模糊测试 最先发布于 The GitHub Blog。
来源:GitHub Blog · AI & ML · github.blog