2025 年 3 月,仅一个被泄露的机器人 PAT(Personal Access Token)就让 tj-actions/changed-files 这个明星 Action 全线沦陷:攻击者把恶意 commit 强制推到历史标签,2.3 万个仓库的 CI 机密被转储到公开日志,企业密钥在几小时内被搜索引擎收录。事件看似是 “令牌保管不善” 的锅,但真正放大冲击波的,却是 GitHub Actions 内建 “包管理器” 长期缺失的三条安全契约:不可变版本、隔离执行、可验证出处。
一、可变标签:官方默认就允许 “穿越”
npm、PyPI 在 2020 年后相继关闭了包的 “重新发布” 开关 —— 版本一旦上传,hash 永不可变。反观 GitHub Marketplace,任何 Action 维护者仍可用 git tag -f 把 v3.0.0 指向全新 commit,平台不做任何二次确认,更不会像容器 registry 那样强制签名。结果就是:
- 用户以为
uses: tj-actions/changed-files@v44是 “稳定引用”,实则背后哈希可被维护者或拿到 PAT 的攻击者任意移动; - 一旦恶意代码被推到旧标签,所有历史工作流在下次触发时自动升级,毫无预警。
缓解参数:
- uses: tj-actions/changed-files@daef52d # 固定 commit SHA
企业级加固:在组织级启用 Actions allow-list,仅白名单 SHA 或自家 Fork 可执行。
二、Runner 环境:机密明文注入,脚本即宿主
GitHub 托管的运行器为了兼容 99% 的存量脚本,把 Secret 直接展开成 shell 变量,攻击者只需在 PR 里植入一行 echo ${{ secrets.GITHUB_TOKEN }} 就能把令牌打印到日志;更隐蔽的写法是 echo 'MALICIOUS' >> $GITHUB_ENV,后续步骤通过环境变量读入,实现 “跨步骤污染”。
2025 年 FreeBuf 的实测显示,自托管 runner 上 /home/runner/work/_temp/*.sh 脚本可被同机监听进程实时读取,机密在落地前就已暴露。
缓解参数:
permissions:
contents: read # 最小化 GITHUB_TOKEN 范围
actions: none
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # 仅在需要步骤内注入
进阶方案:把敏感步骤移入容器化 Job Service,启用 jobs.<id>.container.options: --read-only --tmpfs /tmp 只读根文件系统,阻断回写与命令注入。
三、包缓存:lockfile 即真理,却无人验真
actions/setup-node、setup-python 等官方 Action 默认开启 package-manager-cache: true,缓存 key 仅由 lockfile 哈希生成。攻击者在 PR 中替换 package-lock.json 即可让缓存命中自家恶意包,CI 无需下载就原地 “投毒”。
GitHub 目前没有类似 npm audit 的 “缓存前校验” 钩子,也不支持把缓存 key 与 SBOM 签名绑定。
缓解参数:
- uses: actions/setup-node@v5
with:
node-version: 20
cache: npm
package-manager-cache: false # 关闭自动缓存
替代方案:自建 “干净缓存桶”,在私有 runner 上预装官方依赖 tar,统一通过 npm ci --offline 安装;或启用 Sigstore cosign 在 install 前验证包签名。
四、没有出处就没有信任
GitHub Packages 与 Actions 之间尚无原生 SLSA provenance,下游无法判断 “是谁、在哪、用什么” 构建了包。tj-actions 事件中,攻击者正是利用出处真空,把恶意代码伪装成 “官方发版”,而消费者侧没有任何自动化手段可验证。
可落地清单:
- 在发布工作流里集成
slsa-framework/slsa-github-generator,自动生成符合 SLSA L3 的 provenance.json; - 消费侧使用
slsa-verifier校验出处,失败即阻断 CI; - 把 provenance 哈希写入 GitHub Deployment API,实现 “发布 — 验证” 闭环。
五、小结:缺的不是功能,而是契约
GitHub Actions 作为事实上的 “全球最大包管理器”,日均触发量早已超过 npm 下载量,却仍在用 2019 年的 “信任所有人” 模型运行 2025 年的供应链。可变标签、无隔离执行、无出处校验 —— 三大设计缺陷叠加,让一次 PAT 泄露就能演变成 2 万仓库的密钥雪崩。
短期,把 “固定 SHA + 最小权限 + 关闭缓存” 三条红线写进 CI 模板即可挡住 90% 的 opportunistic 攻击;长期,平台必须提供不可变版本命名空间、默认沙盒运行器与原生 SLSA provenance,否则下一次 tj-actions 只是时间问题。
参考资料
- InfoQ《GitHub 遭入侵凸显 CI/CD 供应链风险》,2025-05
- CSDN《热门 GitHub Action 遭供应链攻击,2.3 万代码库受影响》,2025-03