Back

审计 GitHub Workflows 的安全风险

审计 GitHub Workflows 的安全风险

针对 GitHub Actions 的供应链攻击已从理论层面演变为常态。2025 年 3 月,tj-actions/changed-files 遭到入侵,清晰地展示了一个被污染的 Action 如何迅速在数千个代码仓库中泄露密钥。一年后,Trivy-action 事件再次重演了这一模式——攻击者通过强制推送恶意代码,篡改了 77 个版本标签中的 76 个。

如果您的生产环境中已有 Workflow 在运行,本检查清单可帮助您在无需重新设计整个流水线的前提下,识别出最常见的安全漏洞。

核心要点

  • 在 Workflow 级别设置 permissions: {},并仅为每个 Job 授予所需的最小权限范围。
  • 切勿将 ${{ github.event.* }} 的值直接插值到 Shell 命令中——应改为通过环境变量传递。
  • 将第三方 Action 固定到完整的 Commit SHA,并避免将 pull_request_target 与 Fork 代码的检出操作组合使用。
  • 用 OIDC 替换静态云凭证,并限制自托管 Runner 在公共仓库中执行不受信任的代码。

首先检查 GITHUB_TOKEN 的权限

打开任意 Workflow 文件,查看顶层的 permissions 块。如果该块缺失,则将应用组织的默认配置——而 2023 年 2 月之前创建的组织,其默认权限通常为读写(read-write)。

修复方法非常直接:

permissions: {}  # 在 Workflow 级别拒绝所有权限

jobs:
  build:
    permissions:
      contents: read  # 仅授予 Job 所需的权限

在 Workflow 级别设置 permissions: {} 会强制您明确声明每个 Job 的所需权限。一个仅用于读取代码的 Job,绝不应持有对仓库具有写入权限的 GITHUB_TOKEN

排查 Shell 命令中的不受信任输入

在 Workflow 文件中搜索 run: 块内出现的 ${{ github.event。这是最常见的脚本注入模式:

# 存在风险:攻击者可控制 PR 标题
- run: echo "Checking ${{ github.event.pull_request.title }}"

安全的替代方案是通过中间环境变量传递不受信任的值:

- name: Check PR title
  env:
    TITLE: ${{ github.event.pull_request.title }}
  run: echo "Checking $TITLE"

该值通过环境变量传递,而非直接插值到 Shell 脚本中,因此输入中的 Shell 元字符无法逃逸并执行命令。此外,还需留意在处理用户可控内容的步骤中对 GITHUB_ENVGITHUB_PATH 的写入操作——攻击者可利用这些机制向后续步骤注入环境变量或恶意二进制文件。

审计 pull_request_target 的使用

pull_request_target 在可访问基础分支密钥的上下文中运行,这使其适用于需要对 Fork 发起的 PR 进行评论的 Workflow。风险并不在于触发器本身,而在于将其与 Fork 代码的检出操作组合使用:

# 危险组合
on: pull_request_target
jobs:
  test:
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.sha }}  # 执行攻击者的代码
      - run: npm test  # 同时可访问您的密钥

如果您使用 pull_request_target,请确保没有任何步骤会执行来自 PR 分支的代码。GitHub 在 2025 年底对此进行了部分缓解,但当该触发器与 Fork 检出操作组合使用时,仍属高风险场景。

审查第三方 Action 的版本固定

tj-actions/changed-files 的入侵事件之所以得逞,是因为团队引用了可变标签(如 @v35)。标签的指向可被悄无声息地篡改。固定到完整的 Commit SHA 可防止此类攻击:

# 存在漏洞
- uses: tj-actions/changed-files@v35

# 安全做法
- uses: tj-actions/changed-files@d6babd6899969df1a11d14c368283ea4436bca78

GitHub 现已提供组织级策略,可强制执行 SHA 固定,并使使用未固定 Action 的 Workflow 直接失败。请在组织级别的 Settings → Actions → General 中进行检查。仅靠固定 SHA 还不够——还应考虑在采用新 Action 版本前设置 7 至 14 天的冷却期,因为大多数供应链入侵事件会在一周内被发现。

评估自托管 Runner 的暴露风险

自托管 Runner 默认是持久化的。被攻陷的 Workflow 可以安装在 Job 之间持续存在的后门程序。关键问题在于:是否有公共仓库在使用您的自托管 Runner——如果是,任何贡献者都可以提交 PR,在您的基础设施上执行任意代码。

请在 Settings → Actions → Runners 下检查您的 Runner 组,并确认公共仓库已被排除在外。对于敏感工作负载,建议优先使用即时(JIT)Runner——这类 Runner 在每个 Job 结束后会被销毁。

用 OIDC 替换长期有效的云凭证

如果您的 Workflow 使用以密钥形式存储的静态凭证来向 AWS、Azure 或 GCP 进行身份验证,这些凭证将暴露给该 Job 中的每一个 Action 和步骤。OpenID Connect(OIDC) 通过在运行时签发短期、Job 范围的令牌来消除这一风险。没有可窃取的密钥,也无需手动轮换凭证。

其他值得执行的检查项

  • 制品处理:除非下游步骤明确需要令牌,否则应在 actions/checkout 上设置 persist-credentials: false。这可防止检出令牌在本地 Git 配置中残留,从而被后续 Workflow 步骤访问。
  • 环境保护:部署密钥应存放在配置了必要审批者的 GitHub Environments 中,而非作为普通的仓库密钥存储。
  • 制品证明:对于已发布的软件包,GitHub 的制品证明功能可在构建产物与其源 Workflow 之间建立可验证的关联。
  • OpenSSF ScorecardsScorecards Action 可配置为自动检查令牌权限、固定的 Action 以及脚本注入问题,有助于自动捕获安全回归。

从哪里开始

.github/workflows/ 目录下搜索以下模式:缺失或设置为 write-allpermissions 块、run: 步骤中的 ${{pull_request_target 触发器,以及未包含完整 SHA 的 Action 引用。这四项检查无需任何新工具,即可在大多数仓库中定位出风险最高的问题。

结语

保护 GitHub Actions 的安全并不需要对整个流水线进行彻底重构——现实中的大多数安全事件都可追溯到少数几类反复出现的错误:过于宽泛的令牌权限范围、对不受信任输入的不安全插值、可变的 Action 引用,以及暴露于不受信任代码的 Runner。逐项完成上述检查,即可建立起可靠的安全基线。在此基础上,结合 OpenSSF Scorecards 或类似工具进行自动化扫描,便能在安全回归问题进入生产环境之前将其拦截。

常见问题

GitHub 的代码搜索支持组织范围内的查询。可以搜索 'pull_request_target'、'permissions: write-all' 或 'github.event.pull_request.title' 等关键词,并将范围限定为 path:.github/workflows。如需更深入的分析,Octoscan、zizmor 以及 OpenSSF Scorecards Action 等工具可以扫描整个仓库,并自动报告令牌权限范围、未固定的 Action 以及注入点等问题。

不是的,但这会使更新变得显式可控。您可以在审查版本间差异后,自行决定何时更新 SHA。Dependabot 和 Renovate 等工具可以自动开启 Pull Request 来更新固定的 SHA,让您在享受固定 SHA 安全性的同时,无需承担繁重的维护工作。在合并这些更新前设置 7 至 14 天的延迟,可进一步降低受到新近被入侵版本影响的风险。

对于 AWS、Azure、GCP 以及大多数主流云服务商而言,答案是肯定的。OIDC 令牌具有短期有效性,其作用范围限定于特定的 Workflow 运行实例,且无法被窃取后用于后续攻击。配置过程需要在云服务商侧建立信任关系,但可以彻底消除凭证轮换的负担,并在 Workflow 遭到入侵时将影响范围降至最低。只有在不支持 OIDC 的情况下,静态密钥才是有效的备选方案。

pull_request 触发器在 Fork 的上下文中运行,无法访问基础仓库的密钥,因此适合用于对不受信任的代码运行测试。pull_request_target 触发器则在基础仓库的上下文中运行,可访问密钥,主要用于为 PR 添加标签等任务。需要避免的危险组合,是将 pull_request_target 与 Fork 代码的检出操作混合使用。

Gain control over your UX

See how users are using your site as if you were sitting next to them, learn and iterate faster with OpenReplay. — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay