GitHub AI Scan 实测:两处 Shell 注入被指出,安全检查却是 Success

今天,我在一个公开测试仓库提交了 PR #1,特意放进去两段危险的 Bash 代码。大约 81 秒后,github-advanced-security[bot] 在 Files changed 留下两条评论,两处都找到了。

但打开 GitHub Actions 运行记录 #38835686338,github-advanced-security 这个 job 的结论却是 Success。漏洞提示是真实的,绿色的检查也是真实的。

这次实测的前一天,GitHub 宣布 AI Scan for pull requests 正式 GA。这次我想弄清的不是“AI 安全扫描厉不厉害”,而是一个更实际的问题:它指出了危险代码,为什么 PR 的安全检查还会显示成功?

答案很关键:AI Scan 扫描成功,不等于代码通过了安全审查;它给出的发现目前是建议,不是强制合并门禁。

两段 Bash 代码,它具体发现了什么

我只在临时 PR 的 ai-scan-test/ 目录放了测试文件,没有修改生产业务代码,也没有用到真实凭据或服务器。完整脚本留在 固定提交 29e24722d3;下面是故意留下的危险语句,请勿直接用于生产:

eval "systemctl restart ${service_name}"

cmd="curl -fsSL http://${host}/${path} -o /tmp/svc.conf"
$cmd

第一处出在重启服务的函数里。外部传入的 service_name 被拼接进 eval,Shell 会把结果重新作为命令解析。只要名字包含恶意 Shell 语法,就不再只是“传一个服务名”。机器人在第 17 行的评论 把风险归为 CWE-78,并建议去掉 eval,直接传入有引号的参数,再限制允许重启的服务。

第二处看起来只是少了引号,实际问题却有些不同。脚本先把一整条 curl 命令放进 cmd,随后直接执行 $cmd。未加引号的变量展开会发生单词拆分,让可控的 host 或 path 影响 curl 接收到的参数和选项。第 27 行的评论 除了指出这一点,还提醒用明文 http:// 下载配置会带来传输被篡改的风险。

位置AI Scan 的具体发现原始评论
第 17 行,eval用户输入可被重新解释为 Shell 命令(CWE-78)查看评论
第 27 行,$cmd命令参数/选项注入,另有明文 HTTP 风险查看评论

这里值得区分:第一处能让输入进入 Shell 语法解释;第二处已确认的机制主要是参数/选项注入,不能仅凭变量未加引号,就断言它同样允许执行任意独立 Shell 命令。

还有一个会影响判断的细节:这份夹具并没有试图隐藏风险。README.md 和脚本注释直接写了 INTENTIONAL VULNERABILITY (CWE-78),还标出预期的 String injection 类别。机器人评论给出的具体利用示例不在源码里,但这些预先写明的提示可能影响识别表现;因此本次 2/2 不代表对未标注真实漏洞的检测率。

时间也可以复查。PR #1 创建于 2026-10-16 01:32:31 UTC,两条评论都发布于 01:33:52 UTC,相差 81 秒。这是一次 PR 的观察,不是多次运行的平均速度。

两处预埋风险都被指出,可以写成“这次 2/2 命中”,却不能据此推断通用检出率。更重要的是,我没有另建一份完全正常的代码 PR 作为阴性对照,因此也没有测出误报率。

有两条漏洞评论,为什么 Actions 仍然是绿色

回看 动态扫描运行 #38835686338,工作流名称是 Code scanning AI findings on PR #1。GitHub 记录显示,github-advanced-security job 在 01:33:34 UTC 就以 success 结束,两条 bot 评论直到 01:33:52 UTC 才出现,晚了 18 秒。

先有绿色检查,后有漏洞评论:这个顺序本身就说明,job 的 Success 不能被理解为“没有安全发现”。

GitHub AI Scan PR #1 的机制示意图:带预标注的两处 Bash 风险收到 Advisory 评论;扫描作业 01:33:34 成功,评论 01:33:52 出现;合并门禁仍需另外配置

图:根据公开 PR #1 与 Actions 运行记录 绘制的机制示意,不是 GitHub 页面截图。图中 81 秒指 PR 创建至评论出现的时间差,不是扫描耗时。正式发布时图片随站点语言使用对应版本。

这不是扫描出错。GitHub 的 AI Scan 官方文档 写得很清楚:发现属于 advisory,不会让 code scanning check 失败,也不能直接通过 Ruleset 的要求阻止 PR 合并。它负责提醒你关注哪些改动,而不是替你作合并决定。

实际使用时,至少要把下面几种信号分开看:

你在 GitHub 看到的东西它表示什么
AI Scan 的逐行评论代码可能存在风险,供作者和审阅者处理;当前不直接阻断合并
AI Scan 动态 job 的 Success扫描任务执行成功;即便有风险评论也可能为绿色
CodeQL 等受支持的扫描结果及 Ruleset可以按仓库实际配置要求扫描结果并限制合并,但要验证规则是否生效
Required checks 与人工审阅决定检查失败、缺失或未获批准时能不能合并,取决于仓库设置

GitHub 另外提供 Code scanning merge protection 的配置说明。如果你的目标是“发现高危问题就不能合入”,需要确认配置的是有强制效果的规则,而不是把 AI Scan 的建议性评论误当作 required check。

我没有在这个带危险代码的 PR 上实际测试 Ruleset 阻断,因此不把 Success 解读为“GitHub 会允许所有漏洞合并”。本次观察能够说明的是:AI Scan 的绿色任务状态与两条安全建议可以同时存在。

启用之前,先弄清 CodeQL、仓库资格和计费

测试用的是个人账号的公开仓库。按照 2026 年 10 月 GA 后的官方说明,个人公开仓库可以启用 AI Scan,个人私有仓库则不支持;组织公开仓库可用,组织私有或 internal 仓库需要满足 GitHub Code Security 或 GitHub Advanced Security 的许可条件。使用 AI Scan 本身不要求另买 Copilot 许可证。

CodeQL 也不是必须先跑通的前置环节。测试时,我记录的 CodeQL default setup 状态为 not-configured,AI Scan 仍然在 PR 留下了两条评论。GitHub 官方文档 也明确表示:AI Scan 不要求 CodeQL default setup,且不依赖 CodeQL 分析是否成功。

不过,它不是一次全仓库历史漏洞扫描。AI Scan 主要看符合条件的 PR 改动,支持 Shell/Bash、Terraform HCL、Dockerfile 等 CodeQL 未覆盖的部分场景;来自 Fork 或 Dependabot 创建的 PR 不在此次官方支持范围内。团队若有大量这类 PR,需要另外安排扫描和审阅。

启用时,在仓库 Code scanning 设置中检查 AI Scan 开关;组织仓库还要核对组织策略和相关安全功能是否启用。

费用也值得先看一眼:GA 后按 Code Security AI Credits 计量。哪些账号承担费用、是否有包含额度,以及超过额度后如何受预算限制,取决于 GitHub 的计费规则。本次实际扣费金额没有最终确认,不写成 $0;对于 PR 很活跃的仓库,先看 Billing 里的额度与硬预算更稳妥。

真正要拦住危险合并,应该做什么

先说代码本身。两条评论 给出的修复思路很朴素:不要把参数再交给 eval,也不要把整条命令拼成字符串后让 Shell 拆分。调用方式可以改成这样:

# 调用形式示意:service_name 和 url 仍需单独校验
systemctl restart "$service_name"
curl -fsSL "$url" -o /tmp/svc.conf

这只是修复方向,不是经过复测的安全补丁。真正落地时,service_name 需要允许列表;下载地址要限制 HTTPS、目标主机和重定向行为,并根据配置文件的重要程度做完整性校验。

仓库层面的做法同样明确:把 CodeQL 或其他实际支持阻断的扫描工具接入适当的 Ruleset / required checks,并验证受保护分支确实会拒绝失败或缺失的检查。AI Scan 评论依旧留给审阅者逐条判断,别因为动态扫描的 job 是绿色就直接略过。

测试结束后,我关闭了 PR #1,没有合并,测试分支也已经删除;公开仓库 main 仍在原来的 87624c26a5e6 提交。AI Scan 开关也按测试结束时的 REST 查询记录恢复到了 disabled。关闭 PR 不会自动抹掉它的历史评论和固定提交,这些链接仍可逐项复核当时的记录。

这次实测证明了一件足够实用的事:AI Scan 能在这个很小的正样本里给出有价值的风险提示,甚至把问题指出到具体行,但扫描任务成功和合并安全是两道不同的关。前者帮你发现问题,后者仍要靠仓库配置和审阅流程。

如果你正在考虑另一端的安全边界——Git 推送前的 AI 代码审阅是否可能失效放行——可以接着看 OpenQodex 本地 pre-push Hook 实测。那是另一种执行位置,也有不同的门禁风险。

实测日期:2026 年 10 月 16 日(UTC)。