如果你的公开仓库里有 pull_request_target 触发的工作流,2026 年 11 月 2 日之后它可能直接跑不起来:GitHub 会给公开仓库启用一条默认策略,拦掉这个事件。9 月 17 日"工作流执行保护"正式 GA,配套的默认规则已经在评估模式下运行——你能提前在 Insights 里看到哪些运行会被拦。这篇给一份迁移清单:先自查,再决定是换事件、显式放行还是接受拦截,然后动手配置和加固。
一、这次 GA 发布了什么
"工作流执行保护"(workflow execution protections)是一套 allowlist:actor 规则管"谁能触发",event 规则管"哪些事件能触发",两个条件都在运行前评估。策略可以配在企业、组织、仓库三级,和 ruleset 一样层层叠加。被拦下的运行会失败,日志里能看到类似这样的报错:
Event 'workflow_dispatch' is not allowed to trigger Actions workflows.
Workflow file: '.github/workflows/0-welcome.yml'.从 public preview 转正式 GA 的同时补了三块能力:把规则限定到具体工作流文件(比如只锁 deploy.yml,而不是整个仓库);Policy insights,审计"拦了谁、会拦谁";以及 REST API,把策略当代码批量下发——对几百个仓库的组织来说,最后一条比点鼠标靠谱得多。
还有个默认值要放在心上:actor 规则如果不配,所有对仓库有写权限的人都能触发工作流。收范围之前,先想清楚"写代码的人"和"跑流水线的人"是不是该是同一批人。
二、为什么默认盯上了 pull_request_target
要理解这条默认规则,先分清两种 PR 触发器的信任差异。
pull_request:从 fork 来的 PR,工作流文件取 PR 的 merge commit,运行环境只给只读的 GITHUB_TOKEN、不给 secrets,还有 fork 审批策略兜底。不受信任的代码在低权限环境里跑,这是安全的默认。
pull_request_target:工作流和"未指定 ref 的 checkout"都取自 base 仓库的默认分支,并且被授予 secrets 和可写 token——这是给"需要权限的 PR 自动化"(打标签、发状态检查)准备的。设计前提是:只跑默认分支上的可信代码。
危险出现在你打破这个前提的时候:把 checkout 指向 fork 的 head 或 merge commit,再去执行它。官方文档把这类脆弱写法总结得很清楚:
ref: ${{ github.event.pull_request.head.sha }},或者ref: refs/pull/${{ github.event.pull_request.number }}/merge;repository: ${{ github.event.pull_request.head.repo.full_name }},直接拉 fork 的分支;- 绕过 checkout,用
git fetch、gh pr checkout,或者把 fork 跑出来的 artifact 下载回来再执行。
注意一个容易误判的点:checkout 本身不执行代码,真正引爆的是后面那条把检出内容当代码跑的步骤——make test、npm install(会触发 postinstall 脚本)、构建命令都算。"pwn request"这个叫法来自 GitHub Security Lab,它不是 pull_request_target 独有:issue_comment、workflow_run 工作流如果把 fork 内容当可信数据执行,同样中招。
三、时间线:四个月里的三层防护
把最近的官方动作连起来看,脉络很清楚:
| 时间 | 动作 |
|---|---|
| 2026-06-18 | actions/checkout v7 发布:默认拒绝在 pull_request_target(及由 PR 触发的 workflow_run)中拉取 fork PR 代码,命中不安全输入直接报错 |
| 2026-07-20 | 该防护 backport 到所有受支持的大版本;v1 除外;把版本锁死在 SHA/小版本的工作流不受影响,需要自行升级 |
| 2026-09-17 | 工作流执行保护 GA(原 public preview):新增工作流文件定向、Insights、REST API |
| 2026-11-02 | 公开仓库默认策略从评估转为强制执行,拦截 pull_request_target |
覆盖范围要说准:默认规则只加在"没有已配置适用 event policy 的公开仓库"上,私有和内部仓库不受影响,你自己配过的策略也不会被覆盖。当前处于评估模式——运行照旧,但 Insights 里能看出强制执行后会拦谁。

四、自查:找到会被拦的工作流
第一步,在本地把所有用了这个触发器的工作流列出来:
grep -rn "pull_request_target" .github/workflows/再扫一遍高危写法,命中任何一条,都值得请那个工作流的负责人看两眼:
grep -rnE "refs/pull/|pull_request\.head\.sha|pull_request\.head\.repo" .github/workflows/线上对照两个入口。一个是 Policy insights 页面(在仓库、组织或企业的 Actions 策略页下方),能看到评估模式下"将会被拦"的运行清单;另一个是用 REST API 直接读策略,只读、可审计:
curl -sS \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/OWNER/REPO/actions/policies"组织级把路径换成 /orgs/ORG/actions/policies 即可。接口需要对应层级的管理权限;本文命令依据官方文档核对,未在任何真实仓库执行过。
然后对每个命中工作流回答两个问题:它真的需要 secrets 或可写 token 吗?它会执行来自 PR 的代码吗?两个答案都是"是",才轮到考虑保留。
五、三条路线,选一条
官方给的处置路径其实就是三选一:
- 换事件:不需要 secrets 的,改成
pull_request,保留默认防护,成本最低。 - 显式放行:确实需要高权限的,创建或更新一条适用的 event policy,在允许事件里显式包含
pull_request_target;新版本支持把范围限定到具体工作流文件,别一放就是一整个仓库。 - 接受拦截:确认不再需要的,就让默认策略生效,11 月 2 日之后该事件被拦。
三个选项没有中间地带。最忌讳的是"先放着,等流水线红了再说"——那时候你是在压力下做安全决策。

六、动手:UI 与 REST API 两条路
策略是分层叠加的:企业级配不可协商的兜底,组织与仓库级往上补细节。官方建议拆成多条边界清晰的策略,而不是每个账号一条大而全的规则——出问题时才能定位到底是哪一层拦的。
UI 路径:仓库或组织里进 Settings → Actions → Policies;企业则从 Policies 标签进 Actions → Policies。新建策略时依次选:名称、enforcement(disabled / active / evaluate)、目标范围(仓库或工作流文件)、actor 规则(谁能触发)与 event 规则(哪些事件)。其中评估模式(evaluate)目前仅 GitHub Enterprise 可选,其余情况建议先拿测试仓库小范围验证。
要批量管理,就走 REST API。创建策略的请求体结构和官方示例一致,下面把规则换成事件限制、并显式允许 pull_request_target;执行前请按自己仓库的真实需要调整字段:
curl -sS -X POST \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/orgs/ORG/actions/policies" \
-d '{
"name": "restrict-actions-events",
"enforcement": "active",
"rules": [
{
"type": "restrict_action_events",
"parameters": {
"allowed_events": ["push", "pull_request", "pull_request_target", "workflow_dispatch"]
}
}
]
}'仓库级同形:/repos/{owner}/{repo}/actions/policies;更新用 PUT .../{policy_id},删除用 DELETE。enforcement 选 evaluate 时只评估、不改行为,适合上线前先看一遍会拦到谁;active 才真正生效。
七、必须保留 pull_request_target 的话,四件事做掉
- 收权限:把
GITHUB_TOKEN的permissions收到最小,只挂真的需要的 secrets,别用"全都要"的默认值。 - 认清缓存:pull_request_target 工作流对默认分支缓存只有读权限,写缓存会被拒(保存失败只报 warning,步骤继续)——需要写缓存的活儿挪到
push工作流去做。 - 隔离 runner:自托管 runner 要临时、隔离,别和内部资源混在一起,也别跨运行复用。
- 别碰开关:
allow-unsafe-pr-checkout: true这个选项取名就是让你在代码评审里一眼看见——只有确认"检出的代码只被当数据处理、绝不执行"时才考虑。
另外两件顺手的事:给 Actions 工作流开 CodeQL,能查出注入一类的通用问题;如果自动化里用到 dependabot[bot] 这类机器人身份,记得把它加进允许的 actor,否则它触发的流程会先被拦。
八、排错表:六个常见现象
| 现象 | 优先检查 | 结论边界 |
|---|---|---|
| 11/2 后 CI 报 Event 'pull_request_target' is not allowed… | 该工作流是否真需要此事件;是否需要显式放行 | 报错是策略生效,不代表仓库被限制 |
| Insights 显示将拦截,但运行照常 | 确认处于 evaluate 评估阶段 | 评估期不改行为,别当成失效 |
| 私有仓库没看到这条默认规则 | 规则只针对公开仓库 | 不是配置丢了 |
| 已放行仍被拦 | 策略层级、条件范围、工作流文件定向是否命中 | 放行范围没覆盖,等于没放行 |
| checkout 步骤直接失败(拒绝拉取) | 这是 6 月起 actions/checkout 的默认防护,与 11/2 默认策略是两层 | 两层日志要对号入座,别混为一谈 |
| dependabot 触发的自动化被拦 | allowed actors 是否包含对应机器人身份 | GitHub 内置流程有豁免,不等于你自定义的也豁免 |
九、上线前的验收清单
- 已用 grep 加 Insights 双核对,列出所有使用 pull_request_target 的工作流和负责人。
- 每个工作流都有明确结论:迁移 / 定向放行 / 接受拦截,没有"再看看"。
- 放行范围已最小化:限定到具体工作流文件和必要事件,不做全仓库放行。
- 策略先在评估模式或测试仓库验证过,再切
active。 - 加固四件事(权限、缓存、runner、敏感输入审查)已完成,机器人身份已按需加入。
- 演练做在 11 月 2 日之前,而不是等 CI 变红来提醒你。
参考来源:GitHub 9 月 17 日 GA 公告;Actions policies 官方概念文档;控制工作流执行官方操作文档;Securely using pull_request_target;actions/checkout 默认防护公告;GitHub Security Lab:Preventing pwn requests。资料核对日期:2026 年 9 月 18 日。
11 月 2 日不是一个遥远的日期,离现在只剩六周多。花半小时做上面的自查:要么把这个高权限触发器换成更安全的事件,要么把它锁进最小范围。总比某个早上盯着突然变红的流水线,现场做安全决策强。
🔕 评论已关闭