BYBOWU > Blog > Web development

GitHub Actions pull_request_target: Fix It by Dec 8

blog hero image
GitHub just shipped changes to how pull_request_target and environment branch protections are evaluated—effective December 8, 2025. If your PR workflows gate deployments or touch secrets, some jobs will silently stop matching your environment rules—or worse, keep unsafe access. Here’s exactly what changed, why security researchers have been warning about it, and a step‑by‑step fix you can run this week to keep releases moving without exposing secrets or breaking your CI.
📅
Published
Nov 13, 2025
🏷️
Category
Web development
⏱️
Read Time
12 min

On November 7, 2025, GitHub announced updates that change how GitHub Actions pull_request_target and environment branch protections are evaluated for PR events. The change rolls out on December 8, 2025. If your organization runs checks, leaves automated reviews, labels PRs from forks, or gates deploys behind environments, this update can break patterns you rely on—or finally close a class of security holes you’ve quietly worried about.

Here’s the thing: this isn’t a cosmetic tweak. The source of truth for pull_request_target now always comes from the repository’s default branch, and environment rules evaluate against the executing ref for PR events. That’s good for security, but it’s a policy shift that will surprise any team with creative branch filters or legacy workflow files sitting on maintenance branches.

\"Diagram

What changed in plain English (and why the date matters)

Two changes, one deadline:

1) pull_request_target now sources from the default branch. The workflow file and the commit reference used to execute that workflow are taken from your default branch, regardless of the PR’s base branch. In practice:

  • GITHUB_REF resolves to your default branch (for example, refs/heads/main).
  • GITHUB_SHA points to the latest commit on the default branch when the run starts.

This closes a long‑standing edge case where outdated workflows on other base branches could run privileged jobs for forked PRs. Now, fixes merged to main actually govern pull_request_target behavior.

2) Environment branch protection rules evaluate against the executing ref. For the PR family of events:

  • pull_request (and related review events) evaluate branch rules against refs/pull/<number>/merge.
  • pull_request_target evaluates environment rules against the default branch.

Effective date: December 8, 2025. If your environment filters depend on earlier semantics (like matching a feature branch pattern), those jobs may stop unlocking environments the moment this rolls out.

Why this matters: the security context you can’t ignore

Security teams have flagged pull_request_target as high‑risk for years when used with forked PRs. It runs with the base repo’s privileges, can access secrets, and grants a read/write GITHUB_TOKEN unless you restrict permissions. If you then check out and execute untrusted PR code, you’ve created a fast lane for secret exfiltration or compromised releases. Researchers have demonstrated end‑to‑end compromises by chaining exactly those mistakes.

GitHub’s change reduces the blast radius in two ways: it prevents outdated workflow files on non‑default branches from becoming the execution source, and it aligns environment rule checks with the true execution ref. But—and this is a big but—you can still shoot yourself in the foot if your workflow checks out untrusted code with elevated permissions or broad secrets. The right answer is a mix of least‑privilege tokens, hard gates, and splitting privileged steps into separate workflows.

10‑minute triage: find your risky jobs now

Before you rewrite anything, get an inventory. You can do this in one sprint:

  1. Search workflow files for on: blocks containing pull_request_target, and for any uses of environments: on PR jobs.
  2. Flag jobs that do any of the following on pull_request_target:
    • Run actions/checkout against the PR’s head SHA.
    • Invoke scripts from the PR branch (for example, ./scripts/test.sh from the fork).
    • Use write‑capable tokens or secrets beyond what you must use for the job’s intent (commenting, labeling, or status checks).
  3. List environments referenced by PR jobs. Note any branch filters that assume a named branch pattern (release/*, feature/*) rather than refs/pull/<number>/merge or the default branch.
  4. Check org/repo default permissions. If org default sets GITHUB_TOKEN to read/write, plan to override to read at workflow or job level unless write is essential.

Expect to find three classes of jobs: 1) safe administrative jobs (labeling, comments, path filters) that can keep using pull_request_target; 2) risky test/build jobs that need to move to pull_request or be split; 3) deployment gates that rely on environment filters you’ll need to update.

Fix patterns that work (and the ones that don’t)

1) Keep admin‑only actions on pull_request_target

If a job only needs to add labels, set statuses, or post comments, keep it on pull_request_target but clamp permissions.

permissions:\n  contents: read\n  pull-requests: write\n\non:\n  pull_request_target:\n    types: [opened, synchronize, reopened]\n\njobs:\n  labeler:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/labeler@v5\n        with:\n          repo-token: ${{ secrets.GITHUB_TOKEN }}\n

Note the minimal scope: no checkout of PR code, no extra secrets. This keeps the job useful without exposing the farm.

2) Move build and test of PR code to pull_request

When you need to compile or run tests from a fork, use pull_request. It runs with reduced privileges and blocks secrets by default. If you truly need a secret (say, a private proxy for dependency mirrors), gate that step behind an explicit approval or label and use a narrowly scoped secret.

permissions:\n  contents: read\n\non:\n  pull_request:\n    types: [opened, synchronize, reopened]\n\njobs:\n  pr-tests:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: Run tests\n        run: npm test\n

This change alone eliminates most accidental exposures caused by pull_request_target running untrusted code with secrets.

3) Split privileged steps into a follow‑up workflow

If a PR needs a privileged action—commenting with coverage results, updating labels, or uploading artifacts to a secure bucket—use a second workflow triggered by workflow_run after the pull_request tests pass and a maintainer approves.

on:\n  workflow_run:\n    workflows: [\"PR Tests\"]\n    types: [completed]\n\njobs:\n  privileged-followup:\n    if: ${{ github.event.workflow_run.conclusion == 'success' }}\n    permissions:\n      contents: read\n      pull-requests: write\n    runs-on: ubuntu-latest\n    steps:\n      - name: Post coverage comment\n        uses: owner/coverage-commenter@v1\n

By decoupling, your test job stays safe and your privileged job never touches untrusted code.

4) Update environment branch protection rules

For pull_request jobs that deploy to a preview environment, change rules to match refs/pull/*/merge. For pull_request_target jobs, ensure rules match the default branch, not the PR head. If you previously matched feature/*, those jobs will stop authorizing on December 8.

5) Lock down tokens and caches

Set permissions: contents: read as the default and opt in to pull-requests: write or other scopes only when needed. Avoid saving caches from untrusted PR runs to prevent cache poisoning, and never reuse a cache key derived from untrusted inputs.

What breaks on December 8 (common failure modes)

Based on audits I’ve run for client repos and open source projects, expect these hiccups:

  • Environment rules stop matching for PR deploy jobs that assumed named branch patterns. Symptoms: jobs hang on “waiting for environment approval,” or skip because the environment isn’t authorized.
  • Legacy branch workflows no longer apply to pull_request_target because execution always sources from default. If you carried a policy fix on main but forgot to backport it to release/1.2, that’s fine now—but the inverse is also true: local tweaks on release branches won’t run.
  • Automation that posts comments or labels fails if your repo or org defaulted to read/write tokens and you didn’t scope permissions explicitly. Some actions assume write; if you tightened permissions without adding pull-requests: write, they’ll no‑op.
\"Developer

Should I stop using pull_request_target entirely?

No. It’s appropriate for workflows that must run in the base repository context and don’t execute untrusted code—think labeling, path‑based ownership checks, or policy bots. It’s dangerous when used to check out and run code from a fork with secrets present. The updated semantics make it easier to patch vulnerable workflows centrally on your default branch, but they don’t magically turn unsafe patterns into safe ones.

How do I safely test forked PRs that need secrets?

There are three workable approaches:

  1. Manual approval with pull_request plus environments. Keep the run in the safer trigger, require an environment approval step before any secret‑using commands run, and limit secrets to narrowly scoped credentials.
  2. Split workflows. Use pull_request for untrusted code execution; use a separate workflow_run job for privileged follow‑ups (status comments, artifact publishing).
  3. Use data‑only interfaces. If you must bring results back to the PR (like coverage), post summaries via the API with minimal scopes. Never mount broadly scoped cloud keys into a job that executes PR code.

Does this change affect self‑hosted runners?

Yes, in the sense that the trigger semantics and environment rule evaluation apply regardless of runner type. If you allow public forks to trigger runs on static self‑hosted runners, you’re accepting a much higher risk profile. Use ephemeral or at least isolated runners for any PR execution, and never run untrusted code on long‑lived machines tied to your internal network.

Where to update first: a practical cutover plan

If you’ve got dozens of repositories, tackle them in this order. It’s the highest risk‑reduction per hour invested.

  1. Public repos receiving external PRs. Move build/test to pull_request, keep admin bots on pull_request_target with least privilege, and update environment rules.
  2. Private repos with contractor forks. Treat them as untrusted until you have guarantees, and enforce the same pattern as above.
  3. Repos with deployment previews. Rewrite environment filters to match refs/pull/*/merge, then require approval for any job that exposes secrets.

As you go, document a single “golden workflow” template that teams can copy. Centralizing a safe pattern saves the rework later.

People also ask

Will this change slow down CI?

Not inherently. The change is about where the workflow file and ref come from, plus how environment rules are evaluated. If anything, having one canonical workflow on the default branch reduces divergence and flakiness introduced by stale workflows on release branches.

Do I need to rename environments or branches?

No. You need to update the branch filters tied to environments used by PR events. For pull_request use refs/pull/*/merge; for pull_request_target, ensure your default branch is in scope.

What about tools that comment on PRs?

Keep them on pull_request_target, scope GITHUB_TOKEN to just what they need, and never check out the PR head code in those jobs. Most comment‑only tools don’t need contents: write, just pull-requests: write.

A before/after that avoids a late‑night incident

Before: a monorepo runs pull_request_target to label PRs and run tests. The job checks out the PR head commit, runs npm test, uploads coverage, and posts a comment. The token is read/write, secrets include a private npm token for downloading internal packages.

After: the team splits workflows. Tests run on pull_request with no secrets and a read‑only token. A follow‑up workflow_run job on success posts a coverage comment with pull-requests: write. Environment rules for the preview deploy match refs/pull/*/merge and require maintainer approval. The admin labeler stays on pull_request_target with strict permissions and no checkout. Same functionality, much lower risk—and compliant with the December 8 semantics.

What to do next this week

  • Audit: Identify every workflow using pull_request_target and every PR job tied to environments.
  • Refactor: Move PR code execution to pull_request, split privileged steps, and clamp token scopes.
  • Update environments: Adjust branch filters to match execution refs: refs/pull/*/merge for PR events and your default branch for pull_request_target.
  • Template: Publish a golden PR workflow in a shared repo so teams can adopt a safe default.
  • Verify: Open a test PR from a fork and watch the environment gates, token scopes, and logs. Fix surprises now, not on December 8.

Related deep dives and services

We’ve been covering this change in detail, including concrete YAML rewrites and risk tradeoffs. If you need a battle‑tested checklist, read our guidance on what to change in pull_request_target right now and the follow‑up on shipping the fixes before December 8. For broader CI changes this month, see GitHub Actions: what to fix this week. If you’d like hands‑on help implementing safe patterns across repos, our engineering services team can get you there quickly.

The bottom line

This update is a net win. Centralizing pull_request_target source on the default branch and aligning environment policy checks with execution refs reduces real risks that have bitten large projects. But it does change behavior, and it will surface shaky assumptions around branch filters, token scopes, and where you execute untrusted code.

Make the minimal safe changes now: keep admin bots on pull_request_target with tight permissions, run untrusted code under pull_request, update environment rules to match the new evaluation, and split privileged steps. If you do that, December 8 will be just another Monday—and your PRs will be safer for it.

\"Flowchart
Written by Roman Sulzhyk · BYBOWU
3,665 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

💻
🎯
🚀
💎
🔥