一个程序员在CI脚本里写了三行看似无害的代码,却给整个仓库埋下了远程代码执行的隐患。问题不在他不懂安全,而在于传统的静态分析根本看不到这种攻击路径。

这就是wfguard诞生的背景。它是一个用Go写的命令行工具,专门审计GitHub Actions工作流里的供应链攻击模式。作者用Gemma 4 31B模型做了一个关键设计:让AI去抓那些正则表达式永远抓不到的东西。

打开网易新闻 查看精彩图片

工具的核心是两个引擎并行工作。第一个是确定性的Go分析层,处理规则明确的问题:pwn-request触发器、未验证发布者的可变标签、缺失的permissions块、已知被攻陷的action引用。作者把2025年thetj-actions/changed-files事件直接写进了黑名单。

打开网易新闻 查看精彩图片

第二个是Gemma 4的agent循环。它的任务是跨步骤的污点流分析、action源码审查、以及基于触发面判断严重性。一个真实的案例最能说明两者的差距:某开源项目的release工作流里有这么一段——

export VERSION="${GITHUB_REF#refs/tags/v}"
sed -i "s/version=.*/version=\"${VERSION}\",/" setup.py

$GITHUB_REF来自release触发,值是refs/tags/。Git标签名允许大部分ASCII字符。如果某人推送一个叫v"; rm -rf / #的标签,sed就会把它当成shell命令执行。静态规则抓不到这个:没有${{ ... }}插值可以锚定,只有一个runner环境变量穿过字符串插值进入bash。静态分析看不见这种流转,但agent可以。

作者选了Gemma 4 31B Dense,基于三个模型特性和一个实际问题。256K上下文是硬门槛:工作流YAML本身很小,但引用的action源码很大。调用get_action_source('actions/checkout@v4')会返回action.yml加上dist/index.js,可能是几百KB的打包JavaScript。32K模型不够用,8K模型得预摘要,那就失去了让模型直接阅读的意义。31B是Gemma 4家族里推理最强的dense模型,而多跳污点分析正是小模型失效的地方。

打开网易新闻 查看精彩图片

工具输出三种格式:Markdown报告、SARIF 2.1.0(对接GitHub代码扫描UI)、以及--harden模式下的统一diff。运行wfguard scan ./repo --harden,然后用git apply report.patch即可应用修复。Gemma 4生成修正后的文件,工具会验证YAML解析通过才放进patch,且只包含实际变更的文件。

三个设计选择值得细说。第一,submit_finding是agent唯一的输出通道,模型在工具调用之外说的话全部被忽略。不用严格JSON模式,但结构化输出确保模型不能在schema之外 hallucinate 发现。第二,默认--min-severity high。wfguard会计算卫生类发现(未固定的actions/*标签、缺失permissions块),但默认隐藏。大多数工作流扫描器被这类噪音淹没,而LLM agent把它们当上下文用,人类只在--min-severity low时才看到。第三,UnpinnedRule很窄:只对未验证发布者或有已知入侵历史的action触发。actions/checkout@v4没问题,random-vendor/some-tool@v1才有问题。OpenSSF"全部固定到SHA"的建议理论上正确,但在真实仓库里产生约80%的噪音。

代码开源在github.com/nshekhawat/wfguard。