BYBOWU > Blog > Web development

Fix GitHub Actions pull_request_target by Dec 8

blog hero image
On December 8, 2025, GitHub changes how pull_request_target and environment branch protections work. If your CI expects branch-based environment filters or reads PR code in privileged jobs, you could block deploys—or worse, expose secrets. Here’s what’s actually changing, who gets burned, and a no‑drama plan to harden your workflows in one afternoon. I’ll show safe patterns, quick tests, and the exact YAML tweaks to keep shipping without opening security holes.
📅
Published
Nov 14, 2025
🏷️
Category
Web development
⏱️
Read Time
9 min

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.

Diagram of PR events sourcing from the default branch

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_REF resolve to the default branch; GITHUB_SHA points to its latest commit.
  • Environment branch protection rules evaluate against the executing ref. For pull_request, pull_request_review, and pull_request_review_comment, that’s refs/pull/<number>/merge. For pull_request_target, it’s the default branch. If you matched on release/* or main/develop names 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/checkout with ref: ${{ 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 for pull_request is refs/pull/123/merge.
  • PR labeling/comment bots using pull_request_target and 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:, and environment: 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_target job.

2) Simulate the new refs (25 minutes)

  • Open a test PR and print these values in both pull_request and pull_request_target jobs: ${{ github.ref }}, ${{ github.sha }}, ${{ github.head_ref }}, ${{ github.base_ref }}.
  • Confirm that pull_request jobs see refs/pull/<number>/merge and that pull_request_target jobs resolve to your default branch.

3) Fix environment filters (25 minutes)

  • For pull_request jobs that need environments, add patterns for refs/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_target jobs. If you must inspect files, use the REST API to fetch metadata, not code, or move the step to pull_request with read‑only permissions.
  • Limit permissions:. Start with contents: 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_request
permissions: read-all
jobs.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_target
permissions: contents: read, pull-requests: write
jobs.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_request
jobs.e2e.environment: deploy-staging
environment: 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_requestrefs/pull/<num>/merge; pull_request_target → default branch for GITHUB_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 for push; for PRs, add refs/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_target jobs across the org.
  • Set org/repo default permissions: read-all and escalate per‑job.
  • Add refs/pull/*/merge to environments used by PR jobs; add the default branch for pull_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 in pull_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.
Visual checklist for a 90-minute CI audit

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.
Developer updating GitHub Actions YAML before deadline

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.

Written by Roman Sulzhyk · BYBOWU
3,830 views

Get in Touch

Ready to start your next project? Let's discuss how we can help bring your vision to life

Email Us

[email protected]

We'll respond within 24 hours

Call Us

+1 (602) 748-9530

Available Mon-Fri, 9AM-6PM

Live Chat

Start a conversation

Get instant answers

Visit Us

Phoenix, AZ / Spain / Ukraine

Digital Innovation Hub

Send us a message

Tell us about your project and we'll get back to you

💻
🎯
🚀
💎
🔥