GitHub Actions pull_request_target: Fix by Dec 8
On December 8, 2025, GitHub will change how GitHub Actions pull_request_target behaves for pull‑request events. The workflow file and the checkout reference for this event will come from the repository’s default branch, and environment branch protections will evaluate against the execution ref—closing long‑standing security gaps but also breaking assumptions many teams quietly rely on. If you run labeling/comment bots, auto‑merge gate checks, or release automations off forked pull requests, you need to adjust now.
What’s actually changing on December 8?
There are two material shifts developers need to internalize and test against:
1) The workflow and commit ref for pull_request_target always come from the default branch. Previously, if a PR targeted a non‑default branch (say release/1.2), GitHub could execute the workflow defined on that base branch. After December 8, pull_request_target will always use the workflow from your default branch. This aligns execution with your most up‑to‑date, presumably patched workflows—and eliminates a class of attacks that depended on outdated YAML in long‑lived branches.
2) Environment branch protections evaluate against the execution ref. Environment rules tied to branches will check the branch the workflow actually runs against (the execution ref), not the PR head ref. That reduces unintended secret access during PR runs and harmonizes protections with the execution model.
Both shifts are security wins. They also change the semantics of GITHUB_REF and GITHUB_SHA for pull_request_target: after the change, they’ll resolve to the default branch and its latest commit at run time. If you were parsing those values to decide what to build or deploy, you’ll need to update your logic.
Who’s impacted—and how?
Let’s make this concrete. If any of the following describes you, you’re in the blast radius:
- You run
pull_request_targetworkflows that conditionally check out or build PR code for analysis (linters, doc checkers, comment bots) and assumeGITHUB_REFpoints at the PR’s base branch. - You enforce environment protection rules for PR validation that depend on branch‑specific checks, expecting rules to follow PR heads or non‑default bases during execution.
- You keep release branches with custom workflow YAML that differs from the default branch and expect those YAMLs to be used for PRs targeting those branches.
- You gate publishing, container signing, or infrastructure changes via
pull_request_targetand rely on pre‑changeGITHUB_SHAsemantics in scripts.
Here’s the thing: a lot of teams adopted pull_request_target to unblock fork workflows (commenting, labeling, running bots) because the standard pull_request event restricts secrets. That convenience came with sharp edges—and attackers noticed. The December 8 change is GitHub removing several of those edges without breaking the core promise of the event.
GitHub Actions pull_request_target: why this change now?
Two drivers: security incidents and developer expectations. Over the last few years, researchers and maintainers demonstrated “pwn request” attack patterns where a malicious contributor could use PR metadata or workflow gaps to run code with write‑level GITHUB_TOKEN or with environment secrets. Even without a direct exploit, teams struggled to mitigate risks when outdated workflows on non‑default branches were still used by pull_request_target. By pinning execution to the default branch—and aligning environment protections to the execution ref—patches actually take effect the moment you fix them in main.
Zooming out, this change also reduces cognitive load: rather than reasoning about “which base branch controls the workflow,” you can assume a single source of truth (the default branch) for pull_request_target.
How do I know if my workflows will break?
Start with an inventory. You’re looking for any workflow that uses on: pull_request_target and then does one of the following: checks out the PR head and executes code; uses GITHUB_REF/GITHUB_SHA to determine the build ref; or relies on base‑branch‑specific workflows (e.g., custom YAML in release/*).
gh api repos/:owner/:repo/actions/workflows \
--paginate --jq '.workflows[] | select(.path | test("\.ya?ml$")) | [.name, .path] | @tsv' \
| xargs -n1 -I{} bash -lc 'name=$(echo {} | cut -f1); path=$(echo {} | cut -f2); \
if grep -q "pull_request_target" "$path"; then echo "PRT: $name <$path>"; fi'
Alternatively, search in‑repo: language:YAML "pull_request_target" path:.github/workflows. Make a list, then triage with the framework below.
The PRT Secure‑by‑Default Playbook
Here’s a step‑by‑step migration that teams can complete in under a day for most repos.
1) Classify each PRT workflow
Put every pull_request_target workflow into one of these buckets:
- Comment/label only: posts comments, labels, or statuses; doesn’t build PR code.
- Analyze without executing PR code: parses metadata (title/body/files) but never runs PR artifacts.
- Build or run PR code: checks out head SHA or runs scripts from the PR. Highest risk.
For the first two buckets, you can keep pull_request_target with guardrails. For the third, strongly consider alternatives.
2) Make workflow behavior explicit
Stop depending on defaults. Declare permissions, pin actions by SHA, and explicitly check out the default branch for configuration only.
permissions:
contents: read
pull-requests: write
actions: none
checks: write
jobs:
annotate:
runs-on: ubuntu-latest
steps:
- name: Use default branch workflow repo only
uses: actions/checkout@<PINNED_SHA>
with:
ref: ${{ github.event.repository.default_branch }}
- name: Never checkout PR head
if: always()
run: echo "Do not run untrusted code"
3) If you must touch PR code, move off PRT
Switch to pull_request and run the risky bits behind an explicit approval gate, or run them via workflow_run from a safe, isolated workflow. Two common patterns:
Pattern A: pull_request + manual approvals + minimal token
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read
actions: none
checks: write
jobs:
analysis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<PINNED_SHA>
with: { fetch-depth: 0 }
- name: Run analyzer (no secrets)
run: ./scripts/analyze.sh
gated:
needs: analysis
if: ${{ github.event.pull_request.user.association == 'MEMBER' }}
permissions:
contents: write
steps:
- name: Safe, privileged step for members only
run: ./scripts/member-only.sh
Pattern B: fork‑safe by design using workflow_run
# Safe PR workflow produces artifacts only (no secrets).
on:
pull_request:
jobs:
build-artifacts:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<PINNED_SHA>
- run: ./scripts/build.sh --out dist/
- uses: actions/upload-artifact@<PINNED_SHA>
with: { name: pr-artifacts, path: dist/ }
# Privileged workflow runs in repo context after review.
on:
workflow_run:
workflows: ["PR Build"]
types: [completed]
jobs:
consume:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@<PINNED_SHA>
with: { name: pr-artifacts }
- name: Safe, privileged processing
run: ./scripts/promote.sh
4) Revisit environment secrets and rules
Because protections now evaluate against the execution ref, confirm your environment’s “deployment branches” still match your intent. If your environment allowed release/*, but your pull_request_target runs on main, tighten rules to the default branch and require reviewers for PR‑initiated runs.
5) Update logic that reads GITHUB_REF and GITHUB_SHA
If you were parsing those to branch behavior—building different SDKs for release/* vs main, for instance—switch to explicit inputs from event payload or use github.event.pull_request.base.ref and github.event.pull_request.head.sha when you actually need PR context. Don’t make control decisions based on GITHUB_REF in pull_request_target anymore.
6) Test with a rehearsal PR
Open a PR from a fork hitting a non‑default branch, confirm your workflow still posts comments or labels correctly, and verify that no steps check out or execute head code. Add CI tests for your workflow files if you haven’t already.
People also ask
Does pull_request_target still run for forks?
Yes. That’s the point of the event: it runs in the context of the base repository so you can label, comment, or run trusted automation on external contributions. What’s different after December 8 is where the workflow comes from (always default branch) and how environment protections evaluate (against the execution ref).
Can I keep using environment secrets with PRT?
Carefully—and only when you’re not executing untrusted code. Secrets remain accessible to pull_request_target because the workflow runs in the base repo’s security context. Use them for actions that don’t process attacker‑controlled input. If you need to build or run PR code, move those steps to non‑privileged workflows.
How do GITHUB_REF and GITHUB_SHA change?
After the change, both values point to the default branch and its tip commit during pull_request_target execution. If your scripts used these to infer PR context, switch to the event payload fields instead.
Gotchas that bite seasoned teams
Action pinning still matters. Pin third‑party actions by full commit SHA. Old tags drift; attackers move fast. If you haven’t already, add a lightweight linter that blocks unpinned uses: entries.
Self‑hosted runners and public repos. A pull_request_target that dispatches jobs onto static self‑hosted runners can be catastrophic if any path leads to running untrusted code. Prefer GitHub‑hosted for PRT workflows or isolate self‑hosted runners behind approval gates.
Cache poisoning. Because PRT runs in the base repo context, caches can be shared with trusted branches. Don’t save caches from runs influenced by attacker‑controlled inputs.
Repo rules and status checks. If you require specific checks to pass before merging, confirm those checks still run the way you expect after December 8. Some teams discover their “branch‑specific” required checks now evaluate on the default branch context.
What about related GitHub Actions deadlines?
There’s another timeline nearby you should schedule around: GitHub is retiring the macOS 13 runner image on December 4, 2025, with brownouts that already occurred on November 4, 11, 18, and 25 UTC. If you still pin macos-13, move to macos-15 or the new macos-15-intel labels as applicable. If your team needs a quick primer on that cutover, see our field note GitHub Actions macOS 13 Deprecation: Fix It Now.
A 30‑minute audit checklist
Use this with your largest repos first:
- Enumerate all workflows with
on: pull_request_target. - For each one, confirm it never checks out or executes PR head code.
- Make
permissions:explicit and minimal; removeid-tokenunless you really use OIDC. - Pin every third‑party action by SHA.
- Update logic that reads
GITHUB_REF/GITHUB_SHAto use event properties. - Align environment rules with execution on the default branch; require reviewers for PR‑triggered access.
- Rehearse with a forked PR into a non‑default base branch to validate behavior.
Examples: safe patterns we ship with clients
Comment‑only helper using PRT
on:
pull_request_target:
types: [opened, reopened, synchronize]
permissions:
contents: read
pull-requests: write
jobs:
comment:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@<PINNED_SHA>
with:
script: |
const body = `Thanks for the PR! A maintainer will review shortly.`
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body
})
Hybrid: fast PR checks without secrets + privileged follow‑up
# .github/workflows/pr-ci.yml
on: pull_request
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<PINNED_SHA>
- run: npm ci && npm test --silent
# .github/workflows/approve-and-promote.yml
on:
workflow_run:
workflows: ["PR CI"]
types: [completed]
permissions:
contents: write
jobs:
promote:
if: ${{ github.event.workflow_run.conclusion == 'success'
&& contains(['MEMBER','OWNER'], github.event.sender.association) }}
runs-on: ubuntu-latest
steps:
- run: ./scripts/promote.sh
What to do next (developers)
- Run the audit across your org and fix the top 10 repos by PR volume.
- Add a reusable
actionlintjob to block unpinned actions and unsafe PRT patterns. - Schedule a one‑hour fire drill on December 8 to watch production repos as the change rolls out.
- Document a “no PR head checkout in PRT” policy in your CONTRIBUTING.md.
What to do next (engineering managers)
- Set an org rule: no execution of PR code in
pull_request_target. Period. - Require explicit
permissions:in every workflow. - Track completion: all repos updated, tests rehearsed with a fork PR, owners acknowledged.
- Bundle adjacent work: migrate any lingering
macos-13labels the same week.
When should I avoid pull_request_target entirely?
If the workflow ever builds, runs, or deploys code from an untrusted contributor, avoid pull_request_target. Use the pull_request event with artifact handoff to a privileged workflow_run job. Keep the trust boundary crisp: untrusted code never runs with secrets.
Need a second set of eyes?
We’ve helped teams ship these changes alongside other November fixes—API shifts, runner upgrades, and ruleset cleanups. If you want a review or a turnkey migration, drop us a note via contact or see our services. If you’re already working through our earlier field guides—What to Fix This Week and pull_request_target Changes: Do This Now—this article gives you the why and the updated playbook.
FAQ‑style quick hits
Will my PRT workflow on a release branch still run? Yes, but the workflow definition and ref resolving will come from the default branch. If that workflow doesn’t exist there, create or centralize it—and remove divergent YAML from release branches.
Do I need to change required status checks? Check them. If you required a check tied to a branch that’s no longer the execution ref, update the rules to match how the job now runs.
Can I keep a single workflow file for both PRT and PR? You can, but keep the steps split: the PRT path should be comment/label only; the PR path can build/test without secrets.
Zooming out
Security‑tightening changes like this rarely arrive with much warning and usually land during busy seasons. The upside: once you centralize pull_request_target on your default branch and stop executing PR code under it, you’ll ship faster with fewer late‑night incidents. Take the hour now, codify the safer patterns, and your CI will be both quieter and more predictable in December.