That is summarized in "Github Actions and the threat of malicious pull requests" by Nathan Davison:
When Github first launched Actions in 2018, this was not the case (or at least, it wasn't intended to be) - in Actions terminology, the pull_request event and its variants were the only events that triggered on a PR being opened from a fork, and these events were made to not have access to repo secrets, including having access to a GITHUB_TOKEN value that is read-only.
However, sometime later, in August 2020, the pull_request_target event was added.
This event is given repo secrets and a full read/write GITHUB_TOKEN to boot, however there is a catch - this action only runs in the pull request's target branch, and not the pull request's branch itself.
This differs from the CircleCI approach, which happily checked out the pull request's code when it was instructed to share secrets with PRs from forked repositories, including the pipeline configuration in the pull request (that would allow pull requests to be submitted just to steal tokens and secrets stored within the settings of a CircleCI project).
The blog post confirms:
In order to protect public repositories for malicious users we run all pull request workflows raised from repository forks with a read-only token and no access to secrets.
This makes common workflows like labeling or commenting on pull requests very difficult.
In order to solve this, we’ve added a new pull_request_target event, which behaves in an almost identical way to the pull_request event with the same set of filters and payload.
However, instead of running against the workflow and code from the merge commit, the event runs against the workflow and code from the base of the pull request.
This means the workflow is running from a trusted source and is given access to a read/write token as well as secrets enabling the maintainer to safely comment on or label a pull request.
This event can be used in combination with the private repository settings as well.
Is pull_request_target safe to use?
The pull_request_target event grants workflows triggered by pull requests from forks access to repository secrets and a read/write GITHUB_TOKEN. That is inherently risky if the workflow inadvertently exposes these secrets or allows for unauthorized modifications to the repository.
Another risk: the workflow attempts to check out and execute code from the pull request. Since this event runs in the context of the target branch with access to secrets, executing code from the PR without strict controls can expose secrets to untrusted code.
So it is best to:
- only use
pull_request_target for workflows where access to secrets or write permissions is strictly necessary, such as commenting, labeling, or status updates on pull requests.
- have mechanisms in place to review or sandbox the code before execution if you must use
pull_request_target and need to interact with the code from the pull request: only run certain actions after manual approval via pull request reviews.
- implement conditional logic within your workflows to make sure steps requiring secrets or write permissions are only executed under safe conditions.
- explicitly specify the commit or branch you trust instead of checking out the PR's head commit blindly, when your workflow requires checking out code.
Other best practices: "Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests" by Jaroslav Lobačevski.
Regarding Martin Liversage's comment, Martin wrote:
In May 2026 TanStack had an NPM supply-chain compromise that exploited pull_request_target.
That incident is a concrete illustration of why pull_request_target should not be treated as just a convenient variant of pull_request. In its postmortem, TanStack describes the compromise as chaining the pull_request_target "Pwn Request" pattern, GitHub Actions cache poisoning across the fork/base trust boundary, and runtime extraction of an OIDC token from the GitHub Actions runner process.
The important part for this question is the first link in that chain: a workflow using pull_request_target checked out and executed fork-controlled pull request code. That is exactly the pattern GitHub warns about: running untrusted code under pull_request_target can lead to cache poisoning and unintended access to write privileges or secrets, as noted in the GitHub documentation for pull_request_target and in GitHub Security Lab's article on preventing pwn requests (Aug. 2021).
So the safe rule is: use pull_request_target only for trusted metadata operations on the pull request, like labeling, commenting, or adding reviews. Do not combine it with checking out and building/running the pull request head.
If the workflow needs to test untrusted PR code, use pull_request with minimal permissions. And if privileged follow-up work is needed, split it into a separate trusted workflow, for example using the artifact/result handoff pattern described by GitHub Security Lab: the unprivileged pull_request workflow runs the PR code and uploads passive results, while the privileged workflow_run workflow consumes those results without executing PR-controlled code.
A minimal example would be:
# .github/workflows/pr-ci.yml
name: PR CI
on:
pull_request:
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test
- run: echo "Tests completed for PR #${{ github.event.pull_request.number }}" > result.txt
- uses: actions/upload-artifact@v4
with:
name: pr-result
path: result.txt
and then:
# .github/workflows/pr-follow-up.yml
name: PR follow-up
on:
workflow_run:
workflows: ["PR CI"]
types: [completed]
permissions:
pull-requests: write
jobs:
comment:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
steps:
# Download and inspect only passive data produced by the previous workflow.
# Do not check out or execute the pull request head here.
- run: echo "Post a label/comment/review based on the completed PR CI result"
That split keeps the dangerous part, executing code from the pull request, in an unprivileged workflow, and keeps the privileged part (writing labels/comments/reviews or using secrets) in a workflow that does not execute attacker-controlled PR code.