GitHub 批准了你的 PR,但 pull_request_target 从来没批准过它——这件事今天把 CI/CD 的账全算清楚了
GitHub 批准了你的 PR,但 pull_request_target 从来没批准过它——这件事今天把 CI/CD 的账全算清楚了
你有没有想过这个问题:CI/CD 流水线的”安全审批”,到底在审批什么?
大多数团队的回答是:我们在保护代码质量和发布流程。但很少有人问:我们的 GitHub Actions workflow,在保护什么?
2026 年 7 月 14 日,一个攻击者用 37 个掩护 PR 打开了这个问题的答案。
一行配置,让你的 CI 变成了攻击入口
GitHub Actions 里有一个触发器叫 pull_request_target。它和普通的 pull_request 长得几乎一样,名字也差不多,但行为有本质区别:
pull_request:workflow 在 fork 的上下文里运行,只能读代码,碰不到任何 secrets。pull_request_target:workflow 在主仓库的上下文里运行,有完整的 secrets 访问权限,还有写内容的 GITHUB_TOKEN。
这个设计本来是为了做”对外部 PR 做标注和评论”这类事情。但问题来了:很多 workflow 在 pull_request_target 触发的基础上,还 checkout 了 PR 的代码来执行测试、lint,或者做其他自动化处理。
这就成了安全圈里著名的”pwn request“——外部陌生人的代码,跑在拥有你所有 CI secrets 的上下文里。
7 月 14 日,一个真实项目被这样打穿了
AsyncAPI 是一个流行的开源工具链,GitHub 上有数万星。7 月 14 日,它的 generator 仓库被攻击者静默拿下:
攻击者做的事非常清晰:
- 37 个掩护 PR:全部是添加”慈善捐款页面”内容,标题统一用”ci: update build configuration”,和你见过的任何 CI 更新 PR 一模一样。
- 第 38 个 PR:夹带了一个 markdown 文件,前面垫了约 1000 字节的空白字符,后面藏了混淆过的 JavaScript。
- workflow 执行了这段代码,它扫描 runner 环境,偷走了
asyncapi-bot这个高权限服务账号的 Personal Access Token。 - 拿到 token 后,攻击者推送了一个恶意 commit,触发了 release pipeline,发布了 4 个带后门的
@asyncapi包——合计每天 14 万次下载。
这个攻击不是突然发生的。
4 月 29 日,也就是在此之前 58 天,这个仓库的一个贡献者就提交了一个 PoC(概念验证),证明了这个漏洞的存在。5 月 17 日,他进一步提交了修复方案。
这个修复,在攻击发生的时候,还没被合并。
批准了你的 PR,不等于批准了 CI 的行为
事后复盘里最值得细品的一句话是:“现代 CI/CD 安全实践,特别是 contributor approval 要求,对高知名度仓库是有效的。”
这是 Wiz 研究员的原话。他们说,在这次攻击里,Sentry、OpenSearch、NixOS 这些项目因为配了 PR 审批要求,成功拦掉了攻击。
但这里有一个反常识的边界:
Contributor approval 审批的是”谁能提交代码”,它从来没有审批过”workflow 在有人提交代码后会做什么”。
只要你的 workflow 用了 pull_request_target 且 checkout 了 PR 分支的代码,攻击者就拿到了所有 secrets——不管谁在提交 PR,不管 PR 是不是被审批过的。
这不是代码漏洞,这是 workflow 配置层面的结构性风险。
下一步:怎么知道自己有没有这个问题
打开你的 .github/workflows 目录,搜索这两个组合:
# 如果你的 workflow 同时满足这两个条件,你就是受害者
on: pull_request_target
# 以及
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
只要 checkout 语句引用了 PR 的 head ref,外部代码就跑在了有 secrets 的上下文里。
怎么修
最干净的方案:用 pull_request 替代 pull_request_target,只在确实需要主仓库 secrets 时才用 pull_request_target,且绝对不 checkout 外部 PR 的代码。
如果业务确实需要 checkout PR 代码:
- 在 environment 上配
Required Reviewers,确保 workflow 触发前有人审批 - 在 workflow 文件本身配
pull_request_target的分支限制,只允许受信任分支 - 把 secrets 放在 environment 里而非直接放在 repository secrets,environment 的 protection rules 更细粒度
- 把
pull_request_target的使用当成高危操作,每次改 workflow 文件都走 Code Owner 审批
这件事真正的账
AsyncAPI 的教训不是”我们被攻击了”。教训是:我们明明知道有问题,修补方案就在那,58 天没人合并。
CI/CD 基础设施的安全债务,和应用层代码的安全债务,优先级判断逻辑完全不同。代码漏洞有人管,workflow 配置漏洞没人看。
下一个摊到你的项目,可能不是 58 天,可能更短。
今天就去看一眼你的 .github/workflows。
评论区
登录后可评论。