BYBOWU > Blog > Web development

Dec 8: Fix GitHub Actions pull_request_target

blog hero image
On December 8, 2025, GitHub is changing how pull_request_target runs and how environment branch protection rules are evaluated for pull‑request events. If your workflows depend on branch filters, environment secrets, or checkout behaviors, you could see jobs suddenly fail—or worse, silently run with different refs. Here’s exactly what’s changing, the failure modes we’re already seeing in audits, and a tight remediation playbook you can run this week. No fluff, just the fixes and the...
📅
Published
Nov 15, 2025
🏷️
Category
Web development
⏱️
Read Time
11 min

On December 8, 2025, GitHub changes how GitHub Actions pull_request_target evaluates workflow source and refs, and how environments enforce branch protections for pull‑request events. If you run CI on public forks, gate deployments with environments, or rely on branch patterns for approvals, this update touches you. The fixes aren’t hard, but you do need to be precise—especially if you’ve mixed pull_request_target with checkouts of PR code or depend on environment filters.

Illustration of GitHub Actions workflow with default branch ref and environment gate

What exactly changes on December 8, 2025?

Two coupled updates land the same day:

1) pull_request_target always runs from the default branch. After the change, the workflow file and the checkout ref GitHub uses for GITHUB_REF and GITHUB_SHA will come from your repository’s default branch—regardless of the PR’s base branch. Historically, some teams used backport or release branches as the base; those workflows will no longer be sourced from those branches. Security‑wise, this closes classes of exploits where outdated workflow files on non‑default branches were executed.

2) Environment branch protection rules evaluate against the execution ref. For pull_request family events, environments evaluate against refs/pull/<number>/merge. For pull_request_target, environments evaluate against the default branch. If you filtered environments with patterns like release/* and expected PR jobs to match, you’ll need to adjust.

Why this matters beyond a minor ref tweak

Here’s the thing: lots of pipelines quietly depend on the old semantics. I’ve reviewed repos where a single release/10.x base branch carried a slightly different workflow (paths filters, cache strategy, even different build scripts). On December 8, the running workflow will be pulled from main instead. In the best case, your build logic behaves differently; in the worst case, a deployment environment no longer grants secrets because the ref no longer matches the environment’s branch filter.

There’s also the security angle. pull_request_target gives read/write token permissions and access to secrets by default unless you lock it down. That’s why checking out and executing untrusted PR code in a pull_request_target workflow is a classic footgun. The new default‑branch behavior helps, but it doesn’t eliminate the core risk of combining privileged tokens with attacker‑controlled inputs.

Primary impacts you’ll see on day one

Based on audits we’ve done this month, expect the following hot spots:

  • Environments stop matching: Branch filters like releases/* won’t match refs/pull/<number>/merge. Jobs referencing environments may queue forever waiting for an approval that never appears, or simply skip environment secrets if your gates are misconfigured.
  • Monorepo backport workflows flip source: Any PR targeting release/* will still run, but the workflow file and GITHUB_REF will come from main. If you customized CI per branch, those customizations won’t apply.
  • Audits and cache keys drift: Scripts that computed paths or cache keys assuming the base branch ref now see different values. If you switch architectures (Intel to Apple Silicon) or labels at the same time, cache thrash multiplies.
  • PR labeling/commenting still fine—but builds aren’t: The safe use of pull_request_target is metadata operations (labels, comments). Building PR code there remains risky; keep builds in pull_request and hand results to a privileged workflow_run.

Use the right tool: When to keep or drop pull_request_target

Ask one question: do you need write permissions or environment secrets during the PR event itself? If not, don’t use pull_request_target. Use pull_request with the default read‑only token. If yes, isolate untrusted code from privileges:

  • Pattern A (recommended): pull_request runs build/test with no secrets, uploads artifacts; a separate workflow_run workflow (privileged) reads artifacts and posts results or deploys.
  • Pattern B (gated): Keep pull_request_target for labels/comments only. If you must touch code, require a manual “safe to test” label and never checkout the head commit with credentials persisted.

Remediation checklist you can finish this week

Print this and work down the list. You don’t need a committee meeting to ship these.

  1. Inventory usage. Search your org for on: pull_request_target. Flag any job that checks out github.event.pull_request.head.sha or reads untrusted files.
  2. Reduce permissions. In affected workflows, set permissions: contents: read by default and explicitly enable only what you need per job. Avoid blanket write-all.
  3. Split privileges. Move builds to pull_request; create a privileged workflow_run that comments, updates statuses, or deploys from artifacts.
  4. Fix environment rules. For PR jobs, update environment branch filters to match refs/pull/*/merge. For pull_request_target, add your default branch to the allowed rules.
  5. Lock checkouts. If you must use pull_request_target, set actions/checkout with persist-credentials: false and never checkout the untrusted head with secrets loaded.
  6. Add policy tests. Add a CI check that fails if a workflow uses pull_request_target and requests elevated permissions or has no environment gates. It’s cheap insurance.
  7. Audit caches. Normalize cache keys and proactively delete stale entries. GitHub is enforcing more frequent cache eviction; high‑cardinality keys will churn.
  8. Enable CodeQL on workflows. It now flags common Actions anti‑patterns. Turn it on for the repo and org. It’s free for public repos.
  9. Tighten OIDC policies. OIDC tokens now include check_run_id, which makes it easier to write attribute‑based policies per job. Use it to scope cloud roles.
  10. Document the decision. Add a short SECURITY.md section explaining why you use—or don’t use—pull_request_target. Future‑you will thank you.

FAQ: the “People also ask” version

Should my team stop using pull_request_target entirely?

No. Keep it for PR metadata operations that need privileges (labels, comments, backport automation) and for internal contributors where risk is lower. Don’t build or run untrusted PR code under pull_request_target. If you need to post build results, switch to the pull_requestworkflow_run pattern.

What happens to GITHUB_REF and GITHUB_SHA after the change?

For pull_request_target, both effectively point at the latest commit on your default branch. For the pull_request family, the environment gate evaluates against refs/pull/<number>/merge, which is a synthetic merge ref used during checks. If you assumed GITHUB_REF matched the PR base branch name, you’ll see drift.

Our environment gate is set to “Selected branches: release/*.” Will PR deployments still pass?

Not unless you add a matching pattern for refs/pull/*/merge, or you move the deployment decision to the privileged workflow that runs later. For pull_request_target, add your default branch to the environment’s allowed list.

Is a label like “safe to test” enough?

It’s better than nothing, but it’s not foolproof. Attackers can push new commits after labeling, and humans make mistakes. Use it only as a temporary gate, and never with persisted credentials.

How do forks affect secrets?

Workflows triggered by pull_request from forks run with a read‑only token and no repository or environment secrets. That’s by design. If you promote results (coverage, lint) back to the PR, do it in a separate privileged workflow that reads artifacts.

Concrete examples: safe patterns to copy

Example: build on pull_request, comment via workflow_run. The PR build runs without secrets and uploads a coverage summary as an artifact. A second workflow, triggered by workflow_run on success, reads the artifact and posts a comment with a privileged token. The PR never receives secrets, and you keep the ability to post helpful feedback.

Example: environment‑gated deploy preview. A PR job builds a preview image, uploads it, and marks the artifact with a ref. A privileged workflow, scoped by environment reviewers and branch rules that include the default branch and refs/pull/*/merge, deploys the preview only after manual approval.

Adjacent deadlines you should handle in the same sprint

There’s a cluster of CI changes around the same window. Bundle fixes to minimize churn:

  • macOS 13 runner retirement: Brownouts hit November 11, 18, and 25 (14:00–00:00 UTC), and the image is retired December 4, 2025. Migrate to macos-15 or macos-latest; an macos-15-intel label exists if you still need x86_64.
  • npm classic tokens end November 19, 2025: New classic tokens are already blocked; remaining ones are revoked on the 19th. Move to granular access tokens with write permissions gated by 2FA, or your publishes and provenance steps will start failing.
  • Node 24 becomes default on Actions runners March 4, 2026: Update actions and self‑hosted runners that hard‑pin Node 20 or depend on older OS/arch combos.

If you want a guided plan, our team’s walk‑through covers the moving pieces, from runner labels to reusable workflows. Start with our step‑by‑step Dec 8 Playbook and keep the changes small and observable.

Developer desk with CI checklist and YAML on laptop

A practical, 2‑hour refactor plan

When time is tight, this sequence gets the biggest risk reduction fast:

  1. Kill dangerous checkouts: In every pull_request_target workflow, remove any checkout of the PR’s head or set persist-credentials: false and avoid running package managers or build scripts.
  2. Split the workflow: Move build/test to pull_request. Add a workflow_run that posts comments or updates statuses. Verify you can delete the old privileged build job.
  3. Update environments: Add your default branch and refs/pull/*/merge to allowed refs, or switch the decision to the privileged workflow entirely. Add one “required reviewer” to throttled deploy previews.
  4. Lock permissions: Set permissions: contents: read at the top and grant least privilege per job (e.g., pull-requests: write only where you comment).
  5. Enable CodeQL for workflows: Turn it on and fix the top issues it flags in your actions.
  6. Tighten OIDC trust: Add check_run_id to your attribute‑based policies so cloud roles are bound per job, not per repo.
  7. Ship a safety test: Add a guard job that fails if pull_request_target appears with broad write permissions or without an environment.

Need a second set of eyes? We help teams land these changes quickly and safely. See how we run CI/CD security reviews or talk to us about a 1‑day remediation sprint.

Common edge cases and how to handle them

Backport branches as “default.” Some monorepos treat release/* as de facto defaults for long‑lived LTS lines. After Dec 8, pull_request_target always uses the actual default branch for source and ref. Move branch‑specific logic into reusable workflows and call them conditionally from the default‑branch workflow.

Path‑based config discovery. Teams that derive paths or tenant config from GITHUB_REF will start reading from the wrong branch. Pass configuration as typed inputs or resolve explicitly from github.event.pull_request.base.ref when you need the PR’s base for metadata—not as an execution ref.

Cache poisoning and scope bleed. Remember that caches in pull_request_target share scope with the base branch. Don’t save caches when inputs are untrusted. Prefer pull_request and segregate caches per job matrix key.

Self‑hosted runners. If you rely on pull_request_target to run jobs that touch internal networks, double‑isolate. Use ephemeral runners, pin runner versions, and keep the privileged work in workflow_run after artifacts are sanitized.

What to do next

  • Today: Find every pull_request_target workflow; downgrade permissions; remove untrusted checkouts.
  • Within 48 hours: Split builds to pull_request and wire a workflow_run for comments/status. Fix environment rules and test with a PR from a fork.
  • This week: Turn on CodeQL for workflows, tighten OIDC policies using the new claims, and delete unused environment secrets.
  • This month: Migrate macOS 13 runners and finish the npm token migration before Nov 19 so CI releases don’t fail unexpectedly.

Want a deeper, hands‑on guide to the December changes with copy‑paste snippets? Read our focused explainer: Fix it by Dec 8. And if you’re juggling .NET and mobile pipelines too, our blog keeps a running checklist of platform deadlines that hit build systems.

If you only remember three things

First, pull_request_target now always runs from the default branch—so update any logic that depended on other base branches. Second, environments evaluate PR jobs against the execution ref (refs/pull/*/merge or the default branch). Third, never build untrusted PR code with privileges. Keep privileges and untrusted code in different workflows, and keep your permissions tight.

If you’d like help shipping the fixes across many repos in a day, we do that. Start with a quick note via our contact form and we’ll tailor a short engagement to land the changes safely.

Two‑workflow pattern: pull_request build then privileged workflow_run
Written by Roman Sulzhyk · BYBOWU
4,082 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

💻
🎯
🚀
💎
🔥