GitHub Issue 里藏的不是普通文字:一次针对 Actions 的供应链攻击
研究人员披露针对 GitHub Issue、PR 和评论的攻击:外部文字进入 Actions 的 shell 脚本后可能变成命令。梳理真实证据、触发条件和排查方式。
外部提交的标题、正文和评论应按不可信数据处理,不要直接拼进 Actions 的 shell 脚本;再结合事件类型收紧令牌、密钥与运行器权限。
相关信息
- 研究披露
- Radware 于 2026 年 10 月 29 日发布调查
- 观察范围
- 61 个公开仓库中发现 83 条含恶意内容的事项
- 已确认
- 研究人员在两个工作流案例中看到命令执行证据
- 尚未确认
- 未证实攻击者取得仓库写权限、修改代码或影响下游软件
一条 Issue,怎么碰到了构建服务器
2026 年 10 月 29 日,安全公司 Radware 披露了一起针对 GitHub 仓库自动化流程的攻击。攻击者把恶意内容放进 Issue、Pull Request 或评论;如果仓库的 GitHub Actions 工作流把这些内容直接拼入 shell 脚本,就可能在运行器里执行其中的命令。
这个入口容易被忽略,因为维护者未必需要合并代码:有些工作流在 Issue 创建或评论出现时就自动启动。一次看起来只是处理外部文字的任务,实际上可能运行在能访问构建环境、临时令牌或其他资源的上下文里。具体能接触什么,仍取决于事件类型和工作流配置。
研究人员看到了什么
Radware 的调查将 83 条含已验证恶意内容的 Issue、PR 或评论,关联到 61 个公开仓库;报告称这些事项发表于 2026 年 9 月 1 日,其中大部分集中在约 43 分钟内。83 是事项数量,不是 83 个仓库,也不等于 83 次成功入侵。
研究人员在两个工作流案例中,从实际运行日志找到了命令执行证据。攻击意图包括读取并尝试外传运行环境变量,但报告明确表示,没有证实攻击者因此获得仓库写权限、修改源码或污染下游软件。供应链被进一步控制是可能的后果,不是这次调查已经证实的结果。
报告还提到一个单独的线索:有维护者发现自己的 AI 评论审查 Agent 可能会处理攻击者内容,并能接触敏感凭证。研究人员没有确认该 Agent 被成功利用,也没有确认凭证泄露。它提醒我们检查更多自动化入口,但不能把这起事件直接写成“AI Agent 已被攻破”。
为什么加了引号,仍可能执行
正常情况下,Issue 标题只是一段数据。问题出在处理顺序:GitHub Actions 先把工作流里的表达式替换成实际内容,然后才把生成的脚本交给 shell 解释。如果外部标题被直接插入 run 脚本,标题里的特殊字符就有机会参与 shell 语法解析,而不是老老实实作为文字。
因此,在表达式外面套一层双引号,并不自动把它变安全:shell 拿到的是替换后的整段脚本,不知道其中哪些字符来自外部。GitHub 自己的安全文档也用 PR 标题说明了这种脚本注入。风险发生在数据被放进脚本源代码的那一刻,不是因为打开 Issue 页面本身就会执行命令。
对于只是需要打印标题的步骤,可以先让 Actions 把值放入环境变量,再让 shell 把变量作为被引用的普通参数传给 printf。下面的例子只演示这个具体边界;如果后续把变量交给 eval、再次拼接脚本,或传给会把它当代码解释的程序,风险仍会回来。
env:
ISSUE_TITLE: ${{ github.event.issue.title }}
run: |
printf '%s\n' "$ISSUE_TITLE"先在自己的仓库里找输入,再看它流向哪里
从 .github/workflows 目录入手,找读取 Issue、评论、PR 标题或正文的步骤;分支名、提交信息等外部可控字段也需要同样留意。下面的命令只用于在本地列出部分候选位置,不执行工作流,也不是完整漏洞扫描器。
找到后不要只数表达式出现了多少次。重点是它是否直接进入 run 脚本、命令参数或其他解释器,以及触发这个步骤的人是否能控制对应字段。之后再看该 job 用什么事件触发、能获得哪些权限和密钥、是否使用自托管 runner。
尤其要审查高权限上下文中处理外部 PR 的流程,例如 pull_request_target 相关工作流:它的安全边界与普通 pull_request 不同。若把不可信 PR 代码检出并执行,即使标题本身处理正确,也可能引入另一条执行路径。
rg -n 'github\.event\.(issue|comment|pull_request)' .github/workflows/输入隔离之外,还要缩小执行权限
把不可信内容当作数据处理,是阻断这条命令注入路径的第一步;随后还要减少出错时的影响。按实际需要设置 GITHUB_TOKEN 的最小权限,不向处理外部输入的任务提供不必要的密钥,把发布、签名和部署等高权限步骤与外部触发流程分开。
不要假定所有 Actions 任务都能拿到全部 secrets。GitHub 会根据事件和仓库设置限制凭证,实际权限应逐个工作流核对;反过来也不能因为某一种事件通常受限,就忽略其他触发方式、显式传入的密钥或自托管 runner 的宿主权限。
如果日志显示可疑命令确实运行过,先保存相关运行记录,确认工作流当时可访问的凭证、权限和网络连接,再按实际暴露范围撤销或轮换凭证,并检查仓库与发布记录。不要仅凭一条可疑评论认定所有凭证已经泄露,也不要因未见源码改动就忽略执行证据。
这次事件给 AI 审查工具的提醒
外部 Issue 进入自动化系统,和网页内容进入 Agent 上下文,有一个共同的边界:别人能写入的数据,不能因为被工具读到,就获得执行命令或使用凭证的权力。不过,shell 注入与提示注入的发生机制不同,需要分别排查。
对 AI 审查工具,除了检查提示词,还要看它能调用哪些工具、是否接触发布令牌、是否能把读取的内容发往外部,以及关键动作有没有独立的权限校验。即便模型误把评论当指令,执行层也应该有明确的权限边界。
资料来源
- Radware:GitHub 自动化攻击调查及证据边界 ↗
- GitHub 官方文档:Actions 脚本注入 ↗
- GitHub 官方文档:Actions 安全使用参考 ↗
- GitHub 官方文档:安全使用 pull_request_target ↗
依据研究报告与 GitHub 文档整理;未访问受影响仓库的私有数据或独立复现该攻击。编辑与更正说明。
修订记录
2026-10-30:首次发布:解释真实案例中的脚本注入边界、调查结论与工作流排查方法。