您的位置:首页 > 手游攻略 > GitHub Actions pull_request_target 与 Pwn Request:高权限工作流里的 Fork 代码执行风险(2026)

GitHub Actions pull_request_target 与 Pwn Request:高权限工作流里的 Fork 代码执行风险(2026)

作者:互联网  时间: 2026-07-21 18:26:03  

TL;DR

  • 场景:GitHub 在 actions/checkout v7 中默认拦截 pull_request_target / workflow_run 事件里常见的 Fork PR Head/Merge Checkout 模式,但组织里的旧工作流、第三方 Action、Artifact 与手工 Git 操作并不在新保护覆盖范围内。
  • 结论:Pwn Request 的本质是"高权限事件 × 攻击者可控内容 × 执行路径 × 可滥用资源"四个条件同时闭合,新保护只关掉了其中一个常见入口,信任边界必须由组织自己重建。
  • 产出:一份覆盖事件边界、权限矩阵、版本治理、组织扫描、双阶段重构、Policy 与发布前检查的实战指南,可直接用于 2026-07-20 前后的工作流审计与迁移。

版本矩阵

功能状态说明
actions/checkout v7 2024-06-18 默认拦截 pull_request_target / workflow_run 中 Fork Checkout✅ 已验证2024-06-18 发布,2026-06-29 腾讯新闻等多源报道确认其针对 Pwn Request 强化
allow-unsafe-pr-checkout: true 作为显式豁免✅ 已验证v7 更新日志明确:必须显式声明才能跳过拦截
浮动主版本(如 @v4)可在 Tag 更新后解析到新实现✅ 已验证Renovate 在 2026-06-18 自动升级到 @v7 的 PR 已存在
固定 Commit SHA、Minor 或 Patch 不会自动获得回移✅ 已验证GitHub 官方文档与本文工程推导一致
pull_request 与同仓库 PR 的常规路径不在此次行为变化范围✅ 已验证GitHub Docs 关于 pull_request_target 安全的说明
workflow_runissue_comment、Artifact、Cache、手工 git fetch 也在 Pwn Request 攻击面内✅ 已验证TeamPCP 蠕虫 5-10/5-11 攻击链 + Grafana 5-16 事件均涉及
2026-07-20 为"7-15 编辑注调整后的回移日期"⚠️ 待官方公告核验文章内部标注,未取得 GitHub 官方 changelog 直接确认
v1 不在本次回移范围⚠️ 待验证文章说法,未在官方文档中独立确认
TeamPCP / Shai-Hulud 攻击波及 ~160 个 npm 包,@tanstack/react-router 周下载 ~1200 万✅ 已验证Socket 5-11 通报 + TanStack/Mistral AI 复盘文章
Grafana 2026-05-16 被 CoinbaseCartel 窃取 4 个私有仓库✅ 已验证Grafana 5-17 X 公告 + CSDN 深度复盘
GitHub 自身约 3800 个内部仓库因恶意 VSCode 扩展被 TeamPCP 窃取✅ 已验证搜狐 2026-05-21 报道,与 Pwn Request 是不同事件
npm ci --ignore-scripts 只能减少一类风险✅ 已验证通用供应链最佳实践,本文采用
OIDC Subject 限制 Repo/Ref/Environment 作为防御手段✅ 已验证GitHub Docs OIDC 配置通用做法

pull_request_target 不是一个"更强的 pull_request"。它运行在基础仓库的可信上下文中,通常可以获得基础仓库的 GITHUB_TOKEN、Secret 和默认分支 Cache。如果工作流又把未经审查的 Fork PR 代码拉进来执行,外部贡献者就可能把自己的代码放进高权限执行环境。这类组合被称为 Pwn Request。

GitHub 的新默认保护会让 actions/checkout 拒绝一批常见危险 Checkout 模式,但它不是通用沙箱,也不会识别手工 git fetchgh pr checkout、第三方 Checkout、恶意 Artifact、issue_comment 或所有 workflow_run 组合。正确的迁移不是"关闭报错",而是重建信任边界:低权限工作流处理不可信代码,高权限工作流只处理可信元数据,并通过最小化、可验证的通道交接。

核心结论

  1. 风险由四个条件同时构成:高权限事件、攻击者可控内容、执行路径、可滥用资源。缺少任何一个条件,攻击链都不完整。
  2. actions/checkout 的新默认行为是防呆,不是完整安全边界。组织仍需扫描手工 Git、第三方 Action、Artifact、Cache 和脚本执行。
  3. 浮动主版本可能自动获得回移;固定 SHA、Minor 或 Patch 不会自动获得。固定 SHA 应与持续升级机制组合,而不是被简单视为"更安全"或"更危险"。
  4. 最稳妥的模式是双阶段:pull_request 在只读、无 Secret 环境中构建测试;高权限阶段只读取小型、严格验证的元数据,不执行 Fork 产物。
  5. 7 月 20 日后的工作流失败可能是保护正常生效。绕过检查或设置 allow-unsafe-pr-checkout: true 会恢复原风险,必须经过显式安全评审。

1. 事件边界:GitHub 改变了什么

GitHub 在 2026 年 6 月 18 日发布公告,并在 7 月 15 日的编辑注中把回移日期调整到 7 月 20 日。公告的核心变化是:

  • actions/checkout@v7 默认拒绝 pull_request_target 中常见的 Fork PR Head/Merge Checkout 模式。
  • GitHub 计划把同类保护回移到除 v1 外的其他受支持主版本。
  • 使用 actions/checkout@v4 这类浮动主版本的工作流会在 Tag 更新后解析到新实现。
  • 固定到具体 Commit SHA、Minor 或 Patch 的工作流不会自动获得回移。
  • pull_request 和同仓库 PR 的常规路径不在此次行为变化范围内。
  • 可以使用 allow-unsafe-pr-checkout: true 显式退出保护,但这不是推荐迁移方案。

该公告证明的是"计划、设计与覆盖范围",不证明某一时刻所有 CDN、Tag、Runner 和缓存都已完成传播。发布文章或执行组织审计时,应同时记录:Action 引用、解析 SHA、Runner 镜像、运行时间、错误文本和仓库策略。

2. pull_requestpull_request_target 的权限差异

维度pull_request(Fork PR)pull_request_target
Workflow 来源PR 合并上下文/工作流规则Base 默认分支上的 Workflow
默认执行信任低信任基础仓库高信任上下文
GITHUB_TOKEN通常只读可能按 Workflow/仓库设置获得写权限
仓库 Secret通常不向 Fork PR 暴露可用,取决于 Job 与环境配置
默认分支 Cache低权限任务不应假设可信可能读写,形成跨运行持久化通道
典型用途构建、测试不可信贡献Label、评论、指派、元数据自动化
是否应执行 Fork 代码可以,但必须保持低权限不应直接执行

pull_request_target 的合理用途是对 PR 元数据做高权限动作:添加标签、检查作者、写评论、更新 Project、决定是否允许后续任务。它的错误用途是先进入高权限上下文,再把 Fork 的 Head 拉下来构建。

3. Pwn Request 的成立条件

可以把攻击链写成一个乘法模型:

高权限基础仓库上下文× 攻击者可控的 Fork PR 内容× Checkout / Artifact / 配置进入工作区× Build、Test、Install、Script 等执行路径× Token、Secret、Cache、Artifact 或网络出口= 可利用的 Pwn Request

Checkout 本身通常只是把文件写入磁盘。真正执行攻击者代码的动作可能是:

  • npm installnpm ci 的生命周期脚本;
  • pip install .setup.py、PEP 517 Build Backend;
  • make test、Gradle/Maven Plugin、Cargo Build Script;
  • 读取 PR 中的 Shell、Python、JavaScript 或配置并执行;
  • 测试框架自动加载 Plugin;
  • Docker Build 中的 RUN
  • 从低权限工作流下载 Artifact 后解压并执行;
  • 从攻击者可影响的 Cache 恢复可执行内容。

因此,"只 Checkout、不执行"与"Checkout 后运行任意项目命令"必须分开判断。

4. 一个典型危险工作流

name: preview-pron:pull_request_target:types: [opened, synchronize, reopened]permissions:contents: writepull-requests: writejobs:preview:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v4with:ref: ${{ github.event.pull_request.head.sha }}- uses: actions/setup-node@v4with:node-version: 22- run: npm ci- run: npm test- run: ./scripts/deploy-preview.shenv:PREVIEW_TOKEN: ${{ secrets.PREVIEW_TOKEN }}

危险点不是 pull_request_target 单独存在,而是:

  1. Job 处于 Base 高权限上下文;
  2. ref 指向攻击者控制的 Head SHA;
  3. npm ci 会执行攻击者可修改的依赖和生命周期脚本;
  4. Job 拥有写 Token 和部署 Secret;
  5. 后续脚本还有网络出口和外部平台权限。

actions/checkout 保护可能拒绝此类常见模式。把输入改成手工 git fetch 并不能消除风险,只是绕过一个检测点。

5. 版本固定的真实含义

引用方式可复现性是否自动得到回移主要风险推荐控制
actions/checkout@v4可能Tag 变化会改变行为记录解析 SHA;发布前回归测试
@v4.2.2较高安全修复不会自动进入Dependabot/Renovate + SLA
@<commit-sha>最高长期不更新会滞留漏洞SHA Allowlist + 自动升级 PR
@v1低且过旧不在本次回移缺少新保护和新运行时支持迁移到受支持版本

"固定 SHA"解决供应链可复现性,不解决版本陈旧。组织应要求第三方 Action 固定 SHA,同时设置更新机器人、变更审查和最大滞留时间。

6. 组织级扫描方法

6.1 快速文本搜索

rg -n --glob '.github/workflows/*.{yml,yaml}' 'pull_request_target|workflow_run|issue_comment|gh pr checkout|git fetch|actions/checkout|allow-unsafe-pr-checkout|secrets.|permissions:' .

该命令只能做候选召回。YAML 可以使用 Anchor、Reusable Workflow、表达式和多行字符串,最终仍需结构化解析与人工确认。

6.2 GitHub Code Search 查询

org:YOUR_ORG path:.github/workflows pull_request_targetorg:YOUR_ORG path:.github/workflows "github.event.pull_request.head.sha"org:YOUR_ORG path:.github/workflows "gh pr checkout"org:YOUR_ORG path:.github/workflows "allow-unsafe-pr-checkout"

需要分别检查主仓库、模板仓库、Archived Repository、Fork、Reusable Workflow 和默认分支之外的活动分支。

6.3 风险评分

建议按以下规则评分:

信号分值
触发器包含 pull_request_target+4
Checkout 指向 head.shahead.ref 或 Merge Ref+5
使用 gh pr checkout / 手工拉 Fork+5
Job 使用仓库或环境 Secret+4
permissions 有写权限+3
运行项目脚本、Build、Test、Install+4
读写 Cache+2
下载并执行 Artifact+4
使用 allow-unsafe-pr-checkout: true+6
只有 Label/Comment,且无 Checkout/执行-4

评分只是排序,不是漏洞证明。一个 curl 下载并执行外部脚本的工作流,即使未命中规则也可能高危。

本内容包提供 tools/gh_actions_audit.py,用于候选工作流的本地静态扫描。

7. 安全重构:把不可信计算与高权限动作拆开

7.1 第一阶段:低权限构建和测试

name: pr-cion:pull_request:types: [opened, synchronize, reopened]permissions:contents: readjobs:test:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v4with:persist-credentials: false- uses: actions/setup-node@v4with:node-version: 22cache: npm- run: npm ci --ignore-scripts- run: npm test

这仍然执行攻击者代码,因此不能提供 Secret、写 Token 或高权限环境。--ignore-scripts 只能减少一类风险,测试本身仍是任意代码执行。

7.2 第二阶段:只处理可信元数据

name: pr-metadataon:pull_request_target:types: [opened, edited, labeled, unlabeled]permissions:contents: readpull-requests: writeissues: writejobs:label:runs-on: ubuntu-lateststeps:- name: Apply label from trusted policyenv:GH_TOKEN: ${{ github.token }}PR_NUMBER: ${{ github.event.pull_request.number }}run: |gh pr edit "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --add-label "needs-review"

该 Job 不 Checkout Fork,不读取 PR 中的脚本,不把 PR 标题/正文拼接进 Shell 命令。所有不可信文本通过参数或 API 传递,并避免 eval

7.3 需要把测试结果反馈到 PR 时

安全优先级从高到低:

  1. 使用 GitHub 原生 Check Run/Status,由低权限 Job 直接写其允许写入的状态。
  2. 让高权限 Job 通过 GitHub API 查询低权限 Workflow 的状态,而不是下载可执行 Artifact。
  3. 只传递固定 Schema 的小型 JSON,例如 {run_id, conclusion, commit_sha},验证来源、Workflow、Repository、Branch、SHA、大小和字段。
  4. 不把低权限 Job 生成的脚本、二进制、Node Module、Python Package 或压缩包交给高权限 Job 执行。

一个可接受的高权限评论 Job 应把 run_id 当索引,再向 GitHub API 获取结论;不要信任 Artifact 内自报的"成功"。

8. workflow_run 不是自动安全

workflow_run 可以让一个有 Secret/写 Token 的 Workflow 在低权限 Workflow 完成后运行,因此很适合做权限拆分。但它同样可能被误用:

低权限 Fork Workflow↓ 上传攻击者控制 Artifact高权限 workflow_run↓ 下载、解压、执行 ArtifactSecret / Write Token 泄露

安全交接必须满足:

  • 高权限 Workflow 固定在默认分支;
  • 校验触发 Workflow 的名称、仓库、事件和结论;
  • 校验 head_repositoryhead_sha 与目标 PR;
  • Artifact 设大小上限、文件类型 Allowlist、禁止符号链接和路径穿越;
  • 只解析数据,不执行内容;
  • 需要签名时使用高权限 Workflow 能独立验证、攻击者无法伪造的签名体系;
  • 不让低权限 Workflow 决定高权限 Job 的命令、路径、Action 版本或环境名称。

9. Secret、Token、Cache 与 Artifact 的风险矩阵

资源攻击者目标常见失败模式控制
GITHUB_TOKEN写代码、Release、Issue、PRWorkflow 默认权限过宽顶层 permissions: {},Job 按需开启
Repository Secret外部服务接管Secret 直接注入执行不可信代码的 JobFork Job 无 Secret;Environment 审批
OIDC获取云临时凭据id-token: write 与不可信代码同 JobSubject/Repo/Branch 条件;独立部署 Job
Cache持久化恶意工具链高权限 Job 恢复低信任 Cache信任域分离 Key;高权限 Job 不恢复 Fork Cache
Artifact跨 Job 注入高权限 Job 执行低权限 Artifact固定 Schema、小体积、只解析、来源校验
Self-hosted Runner横向移动Fork 代码进入长期存活机器不对公共 Fork 开放;一次性隔离 Runner
Deployment Token外部平台写权限预览部署脚本由 Fork 控制受信部署器 + 声明式输入 + 人工审批

10. Fork PR 预览环境的安全方案

直接执行 Fork 代码的预览本质上需要不可信计算。可采用:

Fork PR↓ 低权限构建一次性隔离 Runner / 无 Secret / 受限网络↓ 生成内容摘要与不可执行构建产物受信部署服务↓ 策略校验、病毒扫描、大小限制、内容类型限制临时 Preview Namespace↓ TTL、独立域名、无生产 Cookie、无内网访问Review URL

关键不是"Preview 是否自动",而是构建和部署凭据不共处于同一信任域。对于静态站点,可以让构建 Job 生成纯静态文件,并在部署器侧执行严格文件类型检查;对于服务端代码,应使用隔离 Namespace、短期凭据、网络策略和自动销毁。

11. 恶意 Fork 回归测试

在隔离仓库执行,禁止放入真实 Secret。

测试用例

  1. Fork 修改 package.json,加入 preinstall,只创建标记文件。
  2. Fork 修改测试脚本,打印权限和环境变量名称,不打印值。
  3. Fork 尝试写入仓库分支,确认 Token 权限被拒绝。
  4. Fork 尝试访问一个专用无敏感性的 Canary Secret,确认不存在。
  5. Fork 尝试污染 Cache,确认高权限 Job 不恢复该 Cache。
  6. Fork 上传带路径穿越、符号链接、超大文件的 Artifact,确认高权限解析器拒绝。
  7. 对浮动主版本、固定 Patch、固定 SHA 分别运行,记录解析 SHA和错误行为。

记录模板

repository: org/security-sandboxrun_started_at: 2026-07-20T00:00:00Zrunner_image: ubuntu-24.04checkout_ref: actions/checkout@v4resolved_sha: "..."event: pull_request_targetfork: trueexpected: blocked-before-checkoutobserved_status: "..."error_excerpt: "..."secrets_present: falsewrite_token_test: denied

12. 组织级 Policy

最低基线:

  • 默认 permissions: contents: read 或显式空权限;
  • 公共 Fork 不得进入持久化 Self-hosted Runner;
  • pull_request_target 需要 CODEOWNERS 安全审查;
  • 禁止 allow-unsafe-pr-checkout,除非有到期豁免;
  • 第三方 Action 固定 SHA并自动更新;
  • 低信任与高信任 Cache Namespace 分离;
  • workflow_run 高权限 Job 禁止执行上游 Artifact;
  • OIDC Subject 限制 Repo、Ref、Environment;
  • 组织级事件/Actor 执行保护用于限制低信任触发器;
  • Workflow 变更必须经过专门 Reviewer。

示例 Rego 风格策略:

package actions.securitydeny[msg] {input.on.pull_request_targetsome jobstep := input.jobs[job].steps[_]step.uses == "actions/checkout@v4"contains(step.with.ref, "pull_request.head")msg := sprintf("%s checks out fork code in pull_request_target", [job])}deny[msg] {input.on.pull_request_targetsome jobinput.jobs[job].permissions.contents == "write"msg := sprintf("%s has contents:write under pull_request_target", [job])}

真实实现需要先把 YAML 规范化,处理字符串/数组触发器、Anchor、Reusable Workflow 和缺省权限。

13. 发布前检查表

  • 枚举 pull_request_targetworkflow_runissue_comment 与 Reusable Workflow。
  • 识别所有 Fork Head/Merge Checkout,包括手工 Git 和第三方 Action。
  • 识别所有 Build、Test、Install、Docker Build、脚本和可执行 Artifact。
  • 识别 Job 的 Token、Secret、OIDC、Environment、Cache、Artifact 和网络权限。
  • 把不可信计算迁移到 pull_request 低权限 Job。
  • 高权限 Job 只处理固定 Schema 元数据。
  • 将顶层权限收紧,并在 Job 层按需开放。
  • 检查 Checkout 的版本固定和自动升级策略。
  • 在隔离仓库执行恶意 Fork 回归测试。
  • 记录 7 月 20 日解析 SHA、错误文本和 Runner 版本。
  • 对临时豁免设置 Owner、原因、到期日和补偿控制。

14. 常见错误

错误一:把事件从 pull_request_target 改成 pull_request,但继续注入 Secret

Fork PR 默认拿不到仓库 Secret,这通常会直接失败。正确做法是重新设计部署与评论流程,而不是想办法重新暴露 Secret。

错误二:设置 allow-unsafe-pr-checkout: true

这只关闭防护,不改变信任模型。除非工作流后续不执行任何攻击者内容,并经过逐步证明,否则不应使用。

错误三:把 Checkout 改成 git fetch

检测消失,风险仍在。

错误四:认为固定 SHA 可以永久不升级

固定 SHA需要明确的安全更新 SLA。

错误五:高权限 Job 只"解析"Artifact,但使用了不安全解压器或反序列化

路径穿越、符号链接、压缩炸弹、Pickle/YAML 不安全加载都可能把数据通道重新变成代码通道。

15. 限制与待验证

本文没有证明:

  • 2026-07-20 所有 actions/checkout 受支持 Tag 已在所有环境完成传播;
  • GitHub 的默认检测覆盖所有 Pwn Request;
  • 任意组织的 Token/Secret 行为都与公开仓库默认值相同;
  • 一次静态扫描可以替代威胁建模和动态测试。

实际发布前应按 experiments/github-actions-enforcement-test-plan.md 完成隔离验证。

16. 延伸章节

  • 组织级 GitHub Actions Workflow Scanner 与 Policy。
  • Fork PR Preview 的一次性 Runner 和网络隔离。
  • Actions Cache Poisoning 与 Artifact 供应链。
  • Coding Agent 自动生成 Workflow 的安全门禁。
  • Reusable Workflow 的身份、输入与 Secret 继承。

参考来源

  • GH-ACT-01:GitHub Changelog,Safer pull_request_target defaults for GitHub Actions checkout。
  • GH-ACT-02:GitHub Docs,Securely using pull_request_target
  • GH-ACT-03:GitHub Docs,Events that trigger workflows。
  • GH-ACT-04:GitHub Docs,Workflow execution protections。
  • GH-ACT-05:actions/checkout Repository。

错误速查卡

症状根因定位修复
7-20 后 actions/checkout 报"Refused to checkout"或类似阻断v7 默认拒绝 pull_request_target 中 Fork Head/Merge Checkout查看 Job 错误文本 + 解析 actions/checkout 的 Resolved SHA拆分为低权限 pull_request + 高权限元数据 Job,而不是关掉检查
pull_request_target 改成 pull_request 后 Secret 注入失败Fork PR 拿不到仓库 Secret 是设计行为看 Job 是否依赖 secrets.*重构为 API 查询 Check Run,不要绕回暴露 Secret
浮动 @v4 解析到 v7 后行为变化Tag 升级把新默认行为带进来对比 actions/checkout 不同 Tag 之间的差异记录 Resolved SHA,建立 Release 前回归测试
固定 SHA 之后没拿到 v7 保护浮动 Tag 才会自动回移,固定 SHA 不会看 Release 是否覆盖到 Patch/Tag用 Dependabot/Renovate + SLA + 最大滞留时间,而不是不升级
Workflow 从低权限 Artifact 还原"成功"后被攻击高权限 Job 信任 Artifact 内自报结论看 Artifact 来源、签名、Schema高权限 Job 通过 run_id 调 GitHub API 重新查询,不解析 Artifact
7-20 后工作流一直失败,加 allow-unsafe-pr-checkout: true 后恢复这是新保护在生效,不是配置错误比对加豁免前后的错误走显式安全评审,带 Owner、原因、到期日和补偿控制
git fetch 之后 Pwn Request 仍可利用actions/checkout 拦截的不是 Git 本身,而是常见模式看 Workflow 实际执行了哪些脚本信任边界要按"是否执行 Fork 产物"判定,而不是按"是否用了 checkout Action"
npm ci 即使有 --ignore-scripts 仍然有风险--ignore-scripts 只能减少生命周期脚本,不能消除任意代码执行看 Job 是否执行 npm test / 项目脚本把构建/测试放到一次性隔离 Runner,关键步骤才回高权限 Job
高权限 Job "只解析"Artifact 仍被攻击不安全解压器或反序列化把数据通道变代码通道看依赖、路径、符号链接处理文件类型 Allowlist、路径穿越校验、签名验证、不用 Pickle/YAML.load
OIDC 凭据意外泄露id-token: write 与不可信代码同 Job看 Job 是否同时有 pull_request 触发器和 id-token: writeOIDC Subject 限制 Repo/Ref/Environment,部署独立 Job
Self-hosted Runner 被污染公共 Fork 代码进入长期存活机器看 Runner 是否对 Fork 开放Fork 用一次性隔离 Runner,Self-hosted 仅限受信 PR
7-15 编辑注调整到 7-20 后的回移日期文章自带标注,未取得 GitHub 官方 changelog 独立确认看 GitHub 官方 Changelog 公告关注组织实际解析的 SHA、Runner 镜像与错误文本,不依赖具体日历
评分命中但人工判定"安全"评分只是排序,不是漏洞证明看 Workflow 是否实际执行不可信内容用恶意 Fork 回归测试做动态验证,而不是只看规则分
Cache 在高权限 Job 中出现意外二进制低权限 Job 写入了可执行内容到 Cache看 Cache Key 与写入者高权限 Job 不恢复 Fork Cache,Key 按信任域分离
permissions: writepull_request_target 共存Job 拿到高权限 Token 又可被攻击者触发看权限声明位置与默认行为顶层 permissions: {} 空权限,Job 按需开启

最新游戏

更多

Copyright©2010-2019. All rights reserved | 波波三国游戏官网|[email protected]

备案编号:湘ICP备2022015115号-4