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.
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_REFresolves to your default branch (for example,refs/heads/main).GITHUB_SHApoints 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 againstrefs/pull/<number>/merge.pull_request_targetevaluates 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:
- Search workflow files for
on:blocks containingpull_request_target, and for any uses ofenvironments:on PR jobs. - Flag jobs that do any of the following on
pull_request_target:- Run
actions/checkoutagainst the PR’s head SHA. - Invoke scripts from the PR branch (for example,
./scripts/test.shfrom the fork). - Use write‑capable tokens or secrets beyond what you must use for the job’s intent (commenting, labeling, or status checks).
- Run
- List environments referenced by PR jobs. Note any branch filters that assume a named branch pattern (
release/*,feature/*) rather thanrefs/pull/<number>/mergeor the default branch. - Check org/repo default permissions. If org default sets
GITHUB_TOKENtoread/write, plan to override toreadat 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_targetbecause execution always sources from default. If you carried a policy fix onmainbut forgot to backport it torelease/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.
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:
- Manual approval with
pull_requestplus 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. - Split workflows. Use
pull_requestfor untrusted code execution; use a separateworkflow_runjob for privileged follow‑ups (status comments, artifact publishing). - 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.
- Public repos receiving external PRs. Move build/test to
pull_request, keep admin bots onpull_request_targetwith least privilege, and update environment rules. - Private repos with contractor forks. Treat them as untrusted until you have guarantees, and enforce the same pattern as above.
- 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_targetand 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/*/mergefor PR events and your default branch forpull_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.
