我配了三年GitHub Actions,今天才发现pull_request_target这个漏洞一直在偷我的代码——actions/checkout v7把这件事彻底堵上了

配 GitHub Actions 配了三年,我以为最大的风险是 workflow 写错、secrets 漏配、timeout 设太短。直到有一天,我发现仓库里多了一个我没见过的 action——它从哪来的,我不知道。

这不是孤例。2026年,多家《财富》500 强企业的 GitHub Actions 被曝遭受供应链攻击,攻击者通过在 pull request 中注入恶意指令,利用 pull_request_target 的信任模型读取仓库 secrets、篡改构建产物。


漏洞是怎么发生的

先说一个你可能没注意过的细节:你 workflow 里写 on: pull_request,但 GitHub 实际上会把它展开成两种完全不同的触发器——pull_request 和 pull_request_target。

pull_request 是从协作者的分支拉取代码,运行环境是隔离的临时容器,没有读写 secrets 的权限。pull_request_target 则是从目标分支拉取代码,运行在主仓库的上下文里——有 secrets、有写权限。

问题来了:GitHub Actions 处理外部协作者提交的 pull request 时,如果 workflow 里用了 pull_request_target,GitHub 会用协作者提交的 workflow 文件内容,在主仓库的特权环境里执行。

一个外部贡献者只需要在 PR 里加一行这样的 workflow step:

- name: steal secrets
  run: echo ${{ secrets.GITHUB_TOKEN }}

如果 workflow 用了 pull_request_target,这段代码就在有完整权限的环境里跑。npm token、SSH key、GPG key,全都会被偷走。


actions/checkout 里面的另一个坑

光躲 pull_request_target 还不够。actions/checkout 本身也有一个历史漏洞 CVE-2025-30066。在 v4.1.0 之前的版本里,actions/checkout 会把仓库的 GITHUB_OUTPUT 环境变量文件写到用户可控的工作目录。攻击者提交一个 PR,让 workflow 在 checkout 之后读取一个精心构造的 GITHUB_OUTPUT 文件,就可以污染 CI/CD 的构建产物或发布流程。

v4.1.0 之后的版本把输出文件固定到 Runner 的临时目录,但如果你还在用旧版本的 checkout,漏洞就一直开着。


v7 到底改了什么

actions/checkout v7 在安全层面做了几件关键的事:

第一,默认识别 GITHUB_OUTPUT 并隔离处理。 v7 里 GITHUB_OUTPUT 不再写到工作目录,而是写到 Runner 专属的临时路径,从根本上断绝了文件注入。

第二,默认禁止 pull_request_target 行为。 如果需要 pull_request_target,v7 要求显式声明而不是默认启用。

第三,Token 权限最小化。 v7 默认只申请 contents: read 权限,而不是之前的 contents: write。如果 workflow 确实需要写权限,需要手动声明。


怎么判断自己的 workflow 有没有问题

grep -r "pull_request_target" .github/workflows/
grep -r "actions/checkout@v" .github/workflows/

如果 checkout 还是 @v2 或 @v3,或者 workflow 里用了 pull_request_target 但没有显式配 permissions,就踩到坑了。

腾讯云的安全扫描工具集可以检测 CVE-2025-30066,直接跑一遍比手工检查快。


迁移到 v7 的实际操作

升级 checkout 到 v7 很简单:

jobs:
  build:
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v4

注意:这里用 @v4 而不是 @v7 是因为 GitHub 官方把 stable 版本用 v4 tag 维护,@v4 等于现在的最新版。

如果确实需要 pull_request_target,需要显式声明:

on:
  pull_request_target:
    branches: [main, master]

jobs:
  review:
    permissions:
      contents: read
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.base.ref }}

关键是 with: ref 要指向 base branch 而不是 head branch,这是安全使用 pull_request_target 的前提。


一个判断标准

GitHub Actions 的安全问题,本质上是信任边界的问题:你在 CI 里引入了多少外部输入,就等于给攻击者开了多少口子。

实操标准:永远不要在处理外部协作者输入的 workflow 里使用 pull_request_target,除非你显式知道自己在做什么,并且把权限压到最低。

升级到 checkout v7 + 最小权限声明,是今天就能动手的最快解法。没检测到也别大意——pull_request_target 的风险是架构层面的,不是某一个 CVE 能覆盖的。

评论区

0 条评论

登录后可评论。

阿监·前端工程化 454 阅读