GitHub pull_request_target 自查:12 月默认拦截前修工作流

GitHub 在 2026-11-02 的 Changelog 里把公开仓库 pull_request_target 的默认拦截推迟到 2026-12-02,并安排了 11 月 9 日、16 日各两小时的预演拦截。站内已有这条快讯。下面按官方 Changelog、Actions 策略文档和「Securely using pull_request_target」整理一份自查清单:先扫仓库有没有这个触发器,再决定是改成 pull_request、保持默认拦截,还是在策略里精确放行。本站仓库 zhiyong-ai-site 在 2026-11-03 北京时间用 rg 扫过,没有 .github/workflows,也没有 pull_request_target。
先把时间线钉死:预演两次,12/2 才永久拦
Changelog 写明:原计划 11 月 2 日生效,现推迟到 2026-12-02。正式生效前有两次 brownout(预演拦截),每次两小时:
| 窗口 | UTC | 北京时间 | 行为 |
|---|---|---|---|
| 第一次预演 | 2026-11-09 16:00–18:00 | 11-10 00:00–02:00 | 被默认规则拦住的 pull_request_target 运行会失败并带错误注释,工作流不执行 |
| 第二次预演 | 2026-11-16 16:00–18:00 | 11-17 00:00–02:00 | 同上 |
| 正式生效 | 2026-12-02 起 | 同日起 | 公开仓库默认拦截,除非适用的 Actions 事件策略显式允许 |
影响范围是 GitHub Free、Team、Enterprise Cloud 上的公开仓库;私有、内部仓库和 Enterprise Server 不受影响。预演窗口结束后,默认规则会回到 evaluate(评估)模式:工作流仍可跑,但 Insights 里能看到「正式生效后会被拦」的记录。
11 月 9 日第一次预演前就把列表扫完——等窗口里 CI 集体变红,再临时改策略就晚了。
风险点:有密钥的上下文里执行了 fork 代码
官方文档对比了两种事件:
pull_request:工作流文件来自 PR 的 merge commit;对来自 fork 的 PR,GitHub 给只读GITHUB_TOKEN、不发其它密钥,并套 fork 审批策略。pull_request_target:工作流文件和未指定ref的actions/checkout取的是基础仓库默认分支,因此默认会拿到仓库 / 组织密钥和可写令牌——适合给 fork PR 贴标签、分诊、发已认证检查。
危险出现在作者主动改掉默认:把 actions/checkout 指到 github.event.pull_request.head.sha(或 refs/pull/…/merge、fork 的 repository:),紧接着 make test、npm install、npm run build 等会执行检出目录里的脚本、依赖和配置。这就是 Security Lab 说的 pwn request:攻击者只要从 fork 开一个 PR,就能在带密钥的上下文里跑自己的构建链。文档还提醒:同类风险也会出现在 workflow_run / issue_comment 等「有密钥」事件里,只要去下载并执行了不可信代码或工件。
自查第 1 步:把触发器和工作流文件扫出来
在本机对每个要管的公开仓库(或组织镜像)跑只读扫描。下面命令不改任何配置,只列文件:
# 在仓库根目录:列出含 pull_request_target 的工作流
rg -n --glob '.github/workflows/*.{yml,yaml}' 'pull_request_target'
# 同时标出「检出 PR head / merge」的常见写法(高危组合信号)
rg -n --glob '.github/workflows/*.{yml,yaml}' \
'pull_request\.head\.sha|refs/pull/.*/(head|merge)|allow-unsafe-pr-checkout'
# 有 gh 且已登录时:列组织下公开仓库(按需改 ORG)
gh repo list ORG --visibility public --limit 200 --json nameWithOwner \
--jq '.[].nameWithOwner'
2026-11-03 对本站 /workspace/ai-site 执行上述 rg:仓库没有 .github/ 目录,匹配数为 0。你自己的组织请把每个命中文件记进表:路径、是否 checkout 了 PR head、后续步骤有没有 npm/make/pip/cargo 等会执行检出树的命令、用到了哪些 Secrets。
自查第 2 步:先看 Policy insights,再选三种处置之一
Changelog 建议在第一次预演前打开 Actions → Policies 下的 Policy insights,看评估模式里「会被默认规则拦住」的运行。对每个命中工作流,官方给出的决策顺序是:
| 判断 | 做法 | 适合谁 |
|---|---|---|
| 其实不需要密钥 / 写权限 | 把 on: 改成 pull_request(或拆成「不可信侧跑测试 + 可信侧用 workflow_run 读工件」) | 大多数公开仓库 CI |
| 不需要这个触发器 | 删掉或注释掉 pull_request_target,保持 GitHub 默认拦截 | 历史遗留、已无人维护的 YAML |
| 必须保留(贴标签、分诊、已认证检查等) | 在仓库 / 组织 / 企业的 Actions 事件策略里显式允许 pull_request_target,并尽量按工作流文件路径缩小范围 | 开源维护机器人、需要写评论的自动化 |
创建策略的路径(文档「Control workflow execution」):仓库或组织 → Settings → Actions → Policies → 新建策略 → 选择强制状态(Evaluate 可先观察)→ 配置 Restrict events,把需要的事件放进允许列表。企业级在 Policies → Actions → Policies。策略可以叠多层;已有显式事件策略时,不会被「默认拦截」那条替换掉——以你配置的为准。
# 示例:只保留贴标签,不再检出 PR head(示意,按仓库改名)
# .github/workflows/label-fork-prs.yml
on:
pull_request_target:
types: [opened, reopened, synchronize]
permissions:
pull-requests: write
contents: read
jobs:
label:
runs-on: ubuntu-latest
steps:
- name: Add label
# 只用 github.event 元数据;不要 actions/checkout 到 head.sha
run: echo "label via API / softprops 等;勿执行 fork 树"
若确认必须检出 fork 代码且只当数据读、绝不执行:官方要求先通过「是否可用 pull_request」「检出代码会不会被执行」两道题;仍要越过 actions/checkout 保护时才设置 allow-unsafe-pr-checkout: true。文档写明该输入名字故意刺眼,方便代码审查和静态扫描发现。它只覆盖 fork PR 的 checkout 路径;用 git fetch、gh pr checkout 或下载其它工作流工件再执行,不在这个保护范围内。
常见坑
- 以为「私有仓库也一样」。默认拦截只针对公开仓库;私有 / 内部仓库和 Enterprise Server 不在这次 Changelog 范围内,但仍可能有自建策略。
- 只改触发器名字、继续
ref: head.sha+npm test。 换成别的有密钥事件,pwn request 还在。 - 预演窗口当「坏掉的 CI」硬重跑。 窗口内失败是预期行为;应去 Insights 对名单,而不是反复重试同一工作流。
- 组织级一刀放开所有
pull_request_target。 文档建议按工作流路径收窄;全开等于回到默认放开。 - 在
pull_request_target里写缓存。 这类工作流对默认分支缓存默认只读,保存会失败并告警;需要写缓存应放到push等可信触发器。
完成标准
- 组织内每个公开仓库都跑过
rg … pull_request_target,命中文件有清单(或确认 0 命中)。 - Policy insights 里「会被拦」的运行,每条都对应到:已改触发器 / 已删 / 已在事件策略里按文件放行,三者之一。
- 仍保留的
pull_request_target工作流:不把 fork 代码当可执行树;GITHUB_TOKEN权限最小化;没有不必要的组织 Secrets。密钥一旦进日志,按AI 平台密钥泄露自查先吊销再换新。 - 日历已标 11-09、11-16 预演与 12-02 正式生效;值班同学知道窗口内失败是策略预演。
最后
今天先对公开仓库跑一遍 rg,把命中工作流按上表三选一处理,并在 11 月 9 日预演前打开 Policy insights 对过名单。四条完成标准打完勾,12 月默认拦截就是一次排期发布,而不是凌晨的 CI 事故。