Dexaloop

Dexaloop › Tech › Security and privacy

Security and privacy · Explainer

pull_request_target vs pull_request: what it does, why GitHub is blocking it, and how to use it safely

GitHub Actions will block pull_request_target by default in public repositories from 2 December 2026, with test brownouts on 9 and 16 November, so here is what the event does and what to change.

By DexaloopPublished 7 November 20267 min readSources listed below

Key takeaways

  • pull_request_target runs a pull request workflow in the base repository's context, with its GITHUB_TOKEN and secrets. pull_request gives fork pull requests a read-only token and no secrets.
  • The danger is a pwn request: a pull_request_target workflow that checks out the pull request's code and then runs it, handing secrets to an attacker.
  • From 2 December 2026, GitHub blocks pull_request_target by default in public repositories on GitHub Free, Team and Enterprise Cloud, unless an Actions event policy allows it. Private and internal repositories and GitHub Enterprise Server are not affected.
  • Brownouts on 9 and 16 November 2026, 16:00 to 18:00 UTC, enforce the block early. Blocked runs fail with an error annotation.
  • Check Policy insights now, move workflows that do not need secrets to pull_request, and allow pull_request_target only for specific workflow files that never run pull request code.
On this page
  1. pull_request vs pull_request_target in plain terms
  2. Why pull_request_target is risky: pwn requests
  3. What GitHub is changing, and when
  4. How to check whether your workflows are affected
  5. Three ways to fix a pull_request_target workflow
  6. The new actions/checkout safety switch
  7. Quick checklist for maintainers
  8. What is confirmed and what is not stated

pull_request_target is a GitHub Actions event that runs a workflow for a pull request in the context of the base repository. It uses the workflow file from the default branch and gets the base repository's GITHUB_TOKEN and secrets. The plain pull_request event runs the pull request's own merge commit and, for forks, gets a read-only token and no secrets. Because pull_request_target is so often misused, GitHub will block it by default in public repositories from 2 December 2026.

This explainer covers the difference between the two events, why the privileged one is risky, the exact dates of the change, and what maintainers should do before the first brownout on 9 November 2026.

pull_request

Untrusted. Runs the pull request's merge commit. Forks get a read-only GITHUB_TOKEN and no secrets.

pull_request_target

Trusted. Runs the workflow file from the default branch, with the base repository's token and secrets.

Pwn request

A pull_request_target workflow that checks out pull request code and runs it, so the attacker's code gets your secrets.

The block

Default block in public repositories from 2 December 2026, with brownouts on 9 and 16 November.

pull_request vs pull_request_target in plain terms

When someone opens a pull request, GitHub can start a workflow. The question is whose code and whose permissions that workflow gets.

  • pull_request treats the pull request as untrusted. It runs the workflow from the pull request's merge commit, so a contributor's changes are what get built and tested. For a pull request from a fork, the events reference says the GITHUB_TOKEN "has read-only permissions", and other secrets are not passed to the runner.
  • pull_request_target treats the run as trusted. It runs the workflow file from the base repository's default branch, and GitHub Docs say its jobs receive "the base repository's GITHUB_TOKEN and access to repository and organization secrets."

That trust is useful. GitHub names labeling, triage and posting authenticated status checks on fork pull requests as the intended uses. These jobs need write access, but they do not need to run the contributor's code.

pull_requestpull_request_target
Workflow file usedFrom the pull request's merge commitFrom the base repository's default branch
Default actions/checkoutThe merge commitThe default branch
GITHUB_SHALast merge commit of the pull request merge branchLast commit on the default branch
GITHUB_TOKEN for forksRead-onlyThe base repository's token
Secrets for forksNot passed (except GITHUB_TOKEN)Repository and organization secrets
Default activity typesopened, synchronize, reopenedopened, synchronize, reopened
Good forBuilding and testing contributor codeLabels, triage, comments, status checks

Why pull_request_target is risky: pwn requests

pull_request Fork pull requestMerge commit workflowRead-only tokenNo secretspull_request_targetFork pull requestDefault branch workflowBase token and secretsChecks out and runs PR code = pwn request
Which code runs, and with which permissions, for each event.

The problem starts when a pull_request_target workflow fetches the pull request's code and runs it. GitHub calls this a "pwn request" and says the pattern is behind multiple supply-chain compromises. The attacker controls the code, and the code runs with your token and secrets.

Here is the insecure example from GitHub Docs:

# INSECURE. Provided as an example only.
on:
  pull_request_target:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with:
          ref: ${{ github.event.pull_request.head.sha }}
      - name: Test
        run: make test

GitHub points out that the checkout alone is not the exploit. The attack completes in the next step: make test runs a Makefile that came from the pull request. The same thing can happen through npm install, build scripts, config files or dependencies, even when no step looks like it runs code.

GitHub Docs list other shapes of the same mistake:

  • Checking out the head commit or refs/pull/<number>/merge, then building or testing it.
  • Setting repository: to the fork so the fork's branch is pulled in directly.
  • Fetching pull request code another way, such as git fetch, gh pr checkout or a downloaded artifact, and running it.

The docs also note that any event with secrets can have this problem, including issue_comment and workflow_run. Cache poisoning is a further risk if a privileged workflow can write to shared caches. GitHub Docs say pull_request_target runs get read-only access to the default branch cache scope, so they can restore entries but not save them, and that declaring a write-capable cache-mode brings the poisoning risk back.

What GitHub is changing, and when

The GitHub Changelog post of 2 November 2026 sets out the change. GitHub Actions will block pull_request_target events by default in public repositories, unless an Actions event policy explicitly allows them. Enforcement was previously scheduled for 2 November 2026 and now starts on 2 December 2026.

DateWhat happens
Now until 2 December 2026The default rule runs in evaluate mode. Workflows run, with warnings, and insights show which runs would be blocked.
9 November 2026, 16:00 to 18:00 UTCBrownout: the block is enforced. Blocked attempts create failed runs with an error annotation and do not execute.
16 November 2026, 16:00 to 18:00 UTCSecond brownout, same behaviour.
2 December 2026The default block is enforced.

Who is affected

  • Affected: public repositories on GitHub Free, GitHub Team and GitHub Enterprise Cloud.
  • Not affected: private and internal repositories, and GitHub Enterprise Server.
  • The default policy does not replace an Actions event policy you have already configured, according to GitHub Docs.

How to check whether your workflows are affected

  1. Search your repositories for pull_request_target in .github/workflows/.
  2. Open Policy insights. GitHub Docs place it directly under the Actions policies page in the sidebar of the repository, organization or enterprise. It lists runs that would have been blocked.
  3. For each workflow, ask one question: does it need secrets or a write token? If not, move it to pull_request. If it does, check that it never runs pull request code.
  4. Do this before 9 November 2026, as the changelog advises, so the first brownout does not surprise your contributors.

Three ways to fix a pull_request_target workflow

1. Switch to pull_request

This is GitHub's first recommendation when secrets are not needed. For the insecure example above, our adapted version changes the trigger and drops the custom ref, because pull_request already checks out the merge commit (GitHub Docs describe this fix in prose but give no fixed example):

on:
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v7
      - name: Test
        run: make test

The tests now run with a read-only token and no secrets for fork pull requests, which is what untrusted code should get.

2. Split the work into two workflows

Sometimes you need both: test the contributor's code, then post a comment or status with a write token. The GitHub Security Lab guide on pwn requests describes a two-workflow pattern:

  1. An unprivileged workflow on pull_request builds and tests the code, saves the results (such as the pull request number or test output) as artifacts, and exits.
  2. A privileged workflow on workflow_run starts after the first one finishes, downloads the artifacts and acts on them, for example by commenting on the pull request.

The key rule from that guide is that "the untrusted code must be handled via the pull_request trigger". Treat the artifacts as data only. GitHub Docs warn that artifacts from other workflows can come from a fork, so never execute them in the privileged workflow.

3. Keep pull_request_target and allow it on purpose

If a workflow truly needs the base repository's context, such as a labeler that never checks out pull request code, you can keep it. GitHub's guidance:

  • Make sure any checked-out code is "only ever inspected as data and never executed."
  • Give the GITHUB_TOKEN the least permissions it needs, and only the secrets the job uses.
  • Create or update an Actions event policy that explicitly allows pull_request_target. For a repository or organization, go to Settings, then under Actions click Policies. Use the Restrict events rule and target the specific workflow file paths, so other workflows stay blocked.
  • On self-hosted runners, keep them away from internal resources and do not reuse them across runs.
  • Turn on CodeQL for GitHub Actions to scan workflows for these patterns.

Note that the docs say the Evaluate enforcement status for your own policies is available only on GitHub Enterprise Cloud. A policy set to Active blocks non-compliant runs.

The new actions/checkout safety switch

The actions/checkout README says that from v7, checkout "refuses to check out fork pull request code by default" in workflows triggered by pull_request_target or workflow_run. To opt in, you set allow-unsafe-pr-checkout: true, which defaults to false.

GitHub Docs warn that this input covers only fork pull request refs fetched by checkout. It does not cover git fetch, gh pr checkout, other repositories or downloaded artifacts. Setting it is a statement that you have checked the code is never run. Security Lab also notes that persist-credentials defaults to true, so the token is stored on disk for later steps unless you turn it off.

Quick checklist for maintainers

  • List every workflow that uses pull_request_target.
  • Check Policy insights before 9 November 2026.
  • Move test and build jobs to pull_request.
  • Split jobs that need a write token into a pull_request and workflow_run pair.
  • For workflows you keep, remove any checkout of pull request code, cut token permissions, and allow the event for those workflow files only.
  • Close or rebase open pull requests that could still run an old vulnerable workflow version, as Security Lab advises.

Keeping your own devices patched matters just as much as your pipelines. Our guide on how to check your Android security patch level covers the phone side, and more security explainers are on the Dexaloop Tech hub.

What is confirmed and what is not stated

  • Confirmed by GitHub: the 2 December 2026 default block, the 9 and 16 November 2026 brownouts at 16:00 to 18:00 UTC, the affected plans, and that private repositories, internal repositories and GitHub Enterprise Server are excluded.
  • Not stated: the exact text of the error annotation on a blocked run, and the step-by-step form for selecting events in the Restrict events rule. Check them in your own settings rather than relying on third-party screenshots.

Questions

What is the difference between pull_request and pull_request_target?

pull_request runs the workflow from the pull request's merge commit. For a fork, GitHub gives it a read-only GITHUB_TOKEN and passes no other secrets. pull_request_target runs the workflow file from the base repository's default branch, with the base repository's GITHUB_TOKEN and access to repository and organization secrets.

When does GitHub block pull_request_target?

GitHub's changelog says the default block starts on 2 December 2026 in public repositories, unless an Actions event policy explicitly allows the event. It was first planned for 2 November 2026. Two brownouts, on 9 and 16 November 2026 from 16:00 to 18:00 UTC, apply the block early.

Are private repositories affected?

No. GitHub says private and internal repositories are not affected. The change applies to public repositories on GitHub Free, GitHub Team and GitHub Enterprise Cloud, and not to GitHub Enterprise Server.

How do I keep pull_request_target working for one workflow?

Create or update an Actions event policy that explicitly allows pull_request_target, and target it at that workflow file. Policies live in Settings, under Actions, then Policies, for a repository or organization. Do this only if the workflow never runs code from the pull request.

How do I see which workflows the block would stop?

Open Policy insights, which sits under the Actions policies page in the sidebar of the repository, organization or enterprise. Until enforcement, the default policy runs in evaluate mode, so insights show runs that would have been blocked while they still run.

Is a 'safe to test' label enough to protect pull_request_target?

GitHub Security Lab calls label gating only a temporary fix, because new commits can be pushed after the label is added. Keep untrusted code in a pull_request workflow instead.

Sources

Written from the sources above and checked against them before publishing. Found a mistake? Email support@dexaloop.com and we will correct it.

More in Security and privacy