ZHENGJIM.COM / SECURITY OBSERVATIONS资料核对至 2026-10-27 RSS ↗

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 审查工具,除了检查提示词,还要看它能调用哪些工具、是否接触发布令牌、是否能把读取的内容发往外部,以及关键动作有没有独立的权限校验。即便模型误把评论当指令,执行层也应该有明确的权限边界。

资料来源

  1. Radware:GitHub 自动化攻击调查及证据边界 ↗
  2. GitHub 官方文档:Actions 脚本注入 ↗
  3. GitHub 官方文档:Actions 安全使用参考 ↗
  4. GitHub 官方文档:安全使用 pull_request_target ↗

依据研究报告与 GitHub 文档整理;未访问受影响仓库的私有数据或独立复现该攻击。编辑与更正说明。

修订记录

2026-10-30:首次发布:解释真实案例中的脚本注入边界、调查结论与工作流排查方法。

继续阅读本专题 ↗通过 RSS 订阅 ↗