On December 8, 2025, GitHub is changing how GitHub Actions pull_request_target executes and how environment branch protections evaluate for pull‑request jobs. In plain English: privileged PR workflows will always run from your default branch, and environment rules will check the ref that actually executes—not the PR head. If you rely on branch patterns in environments or you ever checked out PR code in privileged jobs, you’ve got work to do this week.
What exactly changes on December 8, 2025?
Two things, both security‑motivated and both easy to verify locally with event payloads:
- pull_request_target always sources from the default branch. The workflow file and ref context come from the repository’s default branch, not the PR’s base branch. Variables like
GITHUB_REFresolve to the default branch;GITHUB_SHApoints to its latest commit. - Environment branch protection rules evaluate against the executing ref. For
pull_request,pull_request_review, andpull_request_review_comment, that’srefs/pull/<number>/merge. Forpull_request_target, it’s the default branch. If you matched onrelease/*ormain/developnames in environments, those filters may no longer behave the way you expect.
Here’s the thing: these are sensible defaults. They cut off entire classes of “pwn request” attacks where outdated workflows or untrusted PR content could steer privileged jobs. But they also break assumptions in a lot of repos.
Why GitHub is doing this now
Security teams have been shouting about risky patterns for years, and with good reason. The toxic combo looks like this:
on: pull_request_target(privileged context, secrets available)- Checkout of PR code via
actions/checkoutwithref: ${{ github.event.pull_request.head.sha }} - Running scripts from that checkout (test runners, build steps, custom utilities)
That’s how you end up with arbitrary code execution in a high‑privilege workflow and potential secret exfiltration. Locking the workflow source to the default branch and aligning environment checks with the execution ref reduces that blast radius.
Who breaks on Dec 8—and why
I’ve audited dozens of orgs this year. The same patterns pop up:
- Branch‑named environments. Environments like deploy-staging with rules
branches: [release/*]no longer match PR jobs, because the execution ref forpull_requestisrefs/pull/123/merge. - PR labeling/comment bots using
pull_request_targetand checking out PR code. They’ll still run, but if they pulled files from the PR in a privileged job, that’s now even more obviously unsafe. You should remove those checkouts regardless. - Rulesets and required checks tied to environment gates. If a job loses access to an environment because the branch filter stops matching, required checks that depend on that job may fail.
- Reusable workflows that assumed head/base branch names. If you pass branch names into reusable jobs for policy, be explicit about the new refs or pass both head/base and execution ref.
- Self‑hosted runners for public repos. If a PR can run code on a static runner, your network is at risk. This change doesn’t fix that—rethink your runner model.
Primary keyword check: GitHub Actions pull_request_target
If you’re scanning for impact by search, this section is for you. Anywhere your workflows or documentation mention GitHub Actions pull_request_target, review the steps that read files, run scripts, or load actions from the PR’s head commit. Those steps must be moved to unprivileged pull_request jobs, or rewritten to operate purely on metadata (labels, comments, safe API calls) while the privileged job remains anchored to the default branch.
The 90‑minute audit to get ahead of the change
Block two focused hours. You’ll leave with a clean bill of health or a short backlog of fixes.
1) Inventory the blast radius (20 minutes)
- Search your org for
pull_request_target,permissions:, andenvironment:in.github/workflows/**.yml. - List every environment referenced by PR‑triggered jobs. Note their branch filters.
- Flag any step that checks out PR code or runs repo scripts in a
pull_request_targetjob.
2) Simulate the new refs (25 minutes)
- Open a test PR and print these values in both
pull_requestandpull_request_targetjobs:${{ github.ref }},${{ github.sha }},${{ github.head_ref }},${{ github.base_ref }}. - Confirm that
pull_requestjobs seerefs/pull/<number>/mergeand thatpull_request_targetjobs resolve to your default branch.
3) Fix environment filters (25 minutes)
- For
pull_requestjobs that need environments, add patterns forrefs/pull/*/merge. Keep your branch filters for push‑based deployments. - For
pull_request_target, add the default branch explicitly, since that’s the execution ref now.
4) De‑risk privileged jobs (20 minutes)
- Ban PR code checkout in
pull_request_targetjobs. If you must inspect files, use the REST API to fetch metadata, not code, or move the step topull_requestwith read‑only permissions. - Limit
permissions:. Start withcontents: read, then grant narrowly per job or step. Only attach secrets to steps that truly need them.
Safe patterns you can copy
Prefer unprivileged checks for PR code
Run builds and tests on pull_request with least privilege:
on: pull_requestpermissions: read-alljobs.build.steps:- uses: actions/checkout@v4- run: npm ci && npm test
Keep privileged jobs metadata‑only
For labeling or commenting, don’t touch PR code:
on: pull_request_targetpermissions: contents: read, pull-requests: writejobs.triage.steps:- name: add label run: gh pr edit ${{ github.event.pull_request.number }} --add-label ready-for-review
Wire environments to the new refs
Give PR jobs access to staging secrets without branch name assumptions:
on: pull_requestjobs.e2e.environment: deploy-stagingenvironment: branches include refs/pull/*/merge
Cut over risky automation
If you previously had a single pull_request_target job that both labeled and ran tests from the PR, split it:
- Job A (pull_request_target): labels/comments only; no code checkout; minimal permissions.
- Job B (pull_request): builds/tests PR code; read‑only token; no secrets.
People also ask
Do I still need pull_request_target after Dec 8?
Sometimes. If you need to comment, label, or run policy with org secrets in a PR context, keep it—but never check out PR code or run untrusted scripts. For CI on the contributor’s code, use pull_request.
How do I filter environments now?
Add refs/pull/*/merge to environment branch rules for PR jobs and your default branch for pull_request_target. Keep your existing push‑based branch filters for deployments off push or workflow_dispatch.
Will required checks or rulesets break?
If a job loses its environment because the filter no longer matches, it might fail, and required checks could block merges. Update environment filters, then validate with a test PR before December 8.
Can I still use reusable workflows?
Yes. Pass explicit inputs for both the execution ref and the PR head/base for logging and policy. Avoid any reusable workflow that automatically checks out PR code in a privileged context.
Data you can plan around
- Effective date: Monday, December 8, 2025.
- Ref semantics:
pull_request→refs/pull/<num>/merge;pull_request_target→ default branch forGITHUB_REF/GITHUB_SHA. - Risk factors: PR‑code checkout in privileged jobs; broad
permissions:; environment secrets tied to branch patterns; static self‑hosted runners on public repos.
Reality check: common traps and how to avoid them
- “We only read a JSON file from the PR.” That’s still executing untrusted code paths (deserializers, scripts, or tool invocations). Keep it metadata‑only or move to unprivileged jobs.
- “Our environment needs branch
release/*.” Fine forpush; for PRs, addrefs/pull/*/merge. Otherwise secrets won’t load and jobs will fail. - “We use a custom token for labels.” Scope it narrowly and expose it only in the step that posts the label. Don’t export it globally in the job environment.
Let’s get practical: a two‑tier migration plan
Tier 1: Immediate guardrails (today)
- Ban PR code checkout in
pull_request_targetjobs across the org. - Set org/repo default
permissions: read-alland escalate per‑job. - Add
refs/pull/*/mergeto environments used by PR jobs; add the default branch forpull_request_target. - Run a test PR to verify secrets, required checks, and rulesets still behave.
Tier 2: Structural improvements (this week)
- Split privileged automation from CI. Triaging, labeling, and policy checks in
pull_request_target; builds/tests inpull_request. - Adopt CodeQL code scanning on public repos. It’s free for public and catches common workflow anti‑patterns.
- Replace static self‑hosted runners on public repos with ephemeral runners or GitHub‑hosted labels.
Where to go deeper
We’ve been tracking and shipping fixes for these changes across client repos the last two weeks. If you want a step‑by‑step walkthrough with before/after YAML and a dry‑run plan, read our detailed breakdown of the PR‑target changes. If you’re juggling multiple CI upgrades, our weekly GitHub Actions fixes and the follow‑up Ship the Fixes Now post will help you triage. Need hands‑on help? See what our team delivers on engineering services or just book time with us.
What to do next
- Today: Run the 90‑minute audit and update environment filters.
- Tomorrow: Split privileged automation from CI and lock down permissions.
- This week: Add CodeQL to public repos and standardize reusable workflows with safe defaults.
- By December 8: Re‑test with live PRs. Confirm secrets exposure is minimized and deploy checks pass.
Zooming out
This change is bigger than a ref variable tweak. It’s a nudge toward a healthier split between unprivileged PR checks and tightly scoped privileged automation. Embrace it. Your incident response budget will thank you, and your contributors won’t notice anything except faster, safer reviews. Make the small changes now, and you’ll glide through December 8 without drama.