小岛AIAI 航行日志

小岛 / 岛刊社 / 文章

GitHub 自动找漏洞能进 CI 吗?这份试跑验收单收好

小岛AI

GitHub 把写测试桩、追覆盖率和崩溃分诊交给了 Agent。启动只要一条命令,团队接入却要验收另一组东西。附官方入口和可复制的试跑验收单。

AI 编程AI 安全Agent 工程

给项目补测试,最怕的场面之一,是工具跑了一夜,早上留下几十份报告,却没人能回答哪份值得修。

维护 C/C++ 解析库、压缩库,或者负责原生依赖的朋友,大概能理解这个尴尬。测试跑起来是一关,找到以前没测到的代码是一关,证明崩溃真能从产品入口触发,又是一关。

GitHub 在 9 月 24 日的官方文章里介绍了 Fuzzing Taskflow,想把这几关里的重复劳动交给 Agent。给它仓库名,它找入口、写测试桩、跑模糊测试,再整理报告。

好家伙,终于有人把「让 AI 帮我测一下」拆成了能检查的工作。

我对它的接入判断很明确。**值得在隔离环境试跑,拿真实产物决定下一步;现阶段把它直接设成每个 PR 的合并裁判,证据还不够。**下面这份验收单,就是给准备试用的开发者和测试负责人写的。

交代一下证据边界。本文核对了官方文档和公开启动脚本,配图来自 GitHub 原文,未实测完整模糊测试流程,也没有自己的漏洞发现成绩。文中的接入方案是工程建议。

一条启动命令,前面还有环境账

项目仓库面向原生 C/C++ 项目。最容易理解的场景,是一个函数吃进文件或字节流,再解析成结构。模糊测试会不断喂它各种输入,观察异常;测试桩则负责把输入送到你真正想测的函数。

你如果写的是普通 Web 业务,别只看见 GitHub 和 AI 就往项目里装。支付金额算错、权限判断漏一个角色,跟解析器读出了内存边界,并不是同一种问题。前者往往需要业务测试和规则审查,这条流水线不能替你补齐。

官方推荐从仓库启动 Codespace,再在仓库目录里运行小项目示例。

./scripts/fuzzing/run_fuzzing.sh DaveGamble/cJSON

参数是目标的 owner/repo,不是网页链接。之后换成你有权测试的仓库即可。命令短,值得收藏,但它不是任意电脑上粘贴就能成功的咒语。

README 的环境要求包括 Linux 或 Codespace、Python 3.11 以上、Git 和 GitHub CLI。流程能安装一些缺失的编译及测试工具,模型服务的配置和鉴权仍需要按项目说明准备。开源代码能下载,不代表调用模型和运行云端机器没有账单。

GitHub 官方的 Codespace 启动截图,展示仓库目录与示例命令;非本文实测

这里有个值得多看一眼的细节。启动脚本顶部注释写着,Agent 的 shell 命令默认需要用户确认;README 的安全说明却明确说,local_shell 不会逐条弹确认,因为那会阻塞无人值守流程。

两段话对不上。仅凭这个差异,还不能断言当前版本究竟怎样执行,但足够提醒我们,不要把注释当权限保证。

我的建议是,试跑用可丢弃的开发环境,只放目标代码和必需凭证。别把日常开发机里的生产配置、发布密钥顺手带进去。代码生成和构建命令在当前环境里执行,Agent 能碰到什么,首先取决于你给这台环境装了什么。

这一步省掉的不是弹窗。省掉的是出问题以后,你还得追查它究竟接触过哪些东西。

覆盖率要能解释,报告要能回放

这工具让我觉得有点子牛逼的地方,是它会拿测试结果决定下一步工作。

按照仓库说明,同一个测试桩会构建出用于模糊测试和用于覆盖率回放的不同二进制文件。跑过的输入还要重新喂回去,看具体走到了哪些代码。模型随后可以补输入、改测试桩,追那些一直没碰到的分支。

GitHub 官方流程示意,依次运行测试、检查覆盖率、改进测试;箭头表示重复迭代

但看覆盖率的时候,我会先问一个很土的问题。它到底测进了哪里?

假设你有个读取配置文件的库。测试桩把绝大多数随机输入都挡在文件头校验,循环跑得热闹,后面的结构解析几乎没进去。此时再给机器多一点时间,未必是最划算的下一步。你更需要一份能越过文件头检查的有效种子,或者检查测试桩有没有把参数接错。

反过来,假设某次改动让覆盖率涨了一截,也得看新走到的是业务关键路径,还是外围工具代码。团队真正关心的解析分支仍然空着,数字再漂亮也不能替它签字。

所以我会把「新增触达的函数和分支」放进试跑记录,同时保留没有覆盖到的重点入口。看一个总百分比,判断不了这些事。

接下来才轮到崩溃。仓库的分诊结果里,有 harness_bug,也有 vulnerability。前者可能是测试桩自身用错了接口,后者是模型给出的漏洞判断。它们都会让终端看起来很严重,处理责任却不同。

你想想看,假设接口要求先初始化上下文,测试桩跳过了初始化,随后把空指针送进去。崩溃可以稳定重现,但能不能算产品漏洞,还得看公开接口的约定,以及真实调用者有没有可能走到这条路径。不能只把栈顶函数名贴进工单,就催维护者合补丁。

这也是我不会让自动分诊直接挡合并的原因。一个原本想省事的工具,如果把测试桩错误全转成开发者的紧急任务,最后只是把加班换了个部门。

GitHub 官方仪表盘示例,包含测试桩状态、覆盖率趋势和构建信息;图中数字属于原作者演示

仪表盘的作用,是让你追问有地方落脚。哪个目标没构建起来,哪份报告对应哪个输入,这轮修改了哪段测试桩,这些都比一句「扫描完成」有用。

仓库也提供了项目方的基准结果。那能帮助理解产物长什么样,但我没有在自己的环境复现,不能顺手写成稳定的检出率,更不能据此替你的项目算节省多少人天。

坦率讲,第一次试跑的目标可以低一点。能把一个目标从源码送到可回放的报告,知道中间哪里失败,比追求一上来就发现新漏洞踏实。

接进团队前,把这张验收单填完

我更愿意把它放在独立试验任务里运行,和现有 CI 分开观察。下面是我按上述边界整理的模板,可以交给测试负责人一起填;它不是 GitHub 官方标准。

试跑验收单
目标仓库与 commit:
本次有权测试的入口:
流水线版本与模型配置:
隔离环境、必要凭证与网络范围:
本次费用和运行时间上限:

构建成功的目标:
构建失败或被跳过的目标及原因:
新增触达的关键函数或分支:
仍未覆盖的重要入口:

每条候选问题必须附上:
最小复现输入
完整复现命令和编译条件
目标版本、测试桩版本
原始错误输出或 sanitizer 栈
从公开接口到异常位置的路径说明
测试桩是否遵守接口约定
补丁前后复现及回归结果

人工复核负责人:
结论:继续试跑/修改配置/暂缓接入

这里最容易漏的,是被跳过的目标。

README 提到,复杂的构建系统可能无法适配 clang 和 AFL 的编译条件,目标会被标记为构建失败并跳过。如果你只数最终报告,零报告里可能混着两件完全不同的事。一件是测过暂时没发现,另一件是根本没有测到。

这两种情况必须在验收时分开。不然下一次有人问「这个库查过了吗」,回答一句「工具跑过了」,话听起来没毛病,风险却原封不动。

再一个,是保留版本。试跑时保存目标 commit、流水线版本和测试桩。模型之后改了测试桩,你手上的复现输入可能仍在,实际触发条件却变了。只留一个仓库名字,下周同事重跑不出来,讨论很容易变成互相猜环境。

费用记录也别只记机器开了多久。模型调用、编译、测试执行和人工复核,是不同的开销。第一次试跑就把它们分开记,后面才能知道卡点在哪。如果大部分时间花在反复修一个无法构建的测试桩上,换更长的运行时间未必有帮助。应该保留失败日志,缩小目标,重新检查编译条件。

还有补丁验收。模型建议改哪里,可以作为线索。真要合进去,我会要求原来的触发输入在修复后不再触发问题,同时合法输入的行为仍符合接口约定。假设补丁靠拒绝所有输入来消灭崩溃,那当然能让报告安静下来,功能也跟着没了。

棒棒的报告要留下证据,棒棒的修复还得保住功能。这不是故意给 Agent 加作业,是给下一个接手的人省时间。

跑完之后,再决定怎么接 CI。我的取舍是,把已经核验过的复现用例转成稳定回归测试,让它跟着代码走;耗时、成本和输出仍有波动的探索流程,则继续按预算单独安排。前者负责防止已知问题回来,后者负责寻找新的盲区。

两项工作分开验收,你才知道一次红灯到底是产品退化,还是探索过程又发现了一个需要人判断的候选。

如果试跑连构建都不稳定,就先修环境和入口。如果已经有可回放的有效报告,下一步再观察人工分诊要花多少时间。别急着拿工具吐出来的报告数量,向团队证明它已经省下了一个安全工程师。

我期待这类工具把补测试的门槛降下来。没人写的测试桩,有人起草了;没人看的未覆盖分支,有人整理出来了。只要这些东西能被复核,就是实打实的进步。

你们团队如果试用这条流水线,最想先验收哪件事——构建成功、关键路径覆盖,还是报告的可复现性?