The pull_request_target trigger is safe only if the workflow never checks out or runs code from the pull request. It runs with the base repository's token and secrets, so running a stranger's build script there can hand them control of your repository. Use pull_request, or split the job in two.
What the trigger does differently
A normal pull_request workflow from a fork gets restricted permissions and no secrets. The pull_request_target trigger runs in the context of the target repository, with a read and write token and access to its secrets. GitHub's own hardening guide says to avoid it if it is not necessary, and says workflows with these triggers must not explicitly check out untrusted code.
The pwn request pattern
GitHub Security Lab describes the dangerous combination: a pull_request_target workflow that checks out the pull request head, then runs something like an install or build. The chain looks like this:
- An attacker opens a pull request from a fork.
- The workflow checks out the attacker's commit.
- A step runs npm install, a test script or a build.
- The attacker's code runs with your write token available.
Install scripts are one path. A changed package.json, build script or test file is another. Even a workflow that never references a secret is exposed because of the write token. Also note that actions/checkout keeps credentials on disk by default, which later steps can read, unless you set persist-credentials to false.
Safer patterns
| Need | Safer approach |
|---|---|
| Run tests on fork pull requests | Use pull_request, which blocks secrets and write access for forks. |
| Comment on or label a pull request after tests | Two workflows: an unprivileged pull_request build that saves results as artifacts, then a privileged workflow_run job that reads them. |
| Run only after a human looks | Label gating, but treat it as a stopgap, since an attacker can push after approval and before the run. |
| Handle pull request files as data | Fine if nothing executes them. |
When you consume artifacts in the privileged job, treat them as untrusted. Reading a pull request number or a coverage figure is acceptable. Running a binary from the artifact is not.
A review checklist for your repository
- Search your workflows for pull_request_target and workflow_run.
- For each hit, ask whether it checks out the pull request head or runs anything from it.
- Set permissions at the job level to the minimum. See least privilege GITHUB_TOKEN for a release workflow.
- Add the workflows folder to CODEOWNERS so changes need a reviewer, as in CODEOWNERS for workflows and release files.
- After you fix a workflow, close or rebase open pull requests, since open ones keep using the workflow version from when they were opened.
Try it yourself
Click through it at your own pace, or open it full screen.
Click through the pull request flow and notice that the check runs before anything merges. That ordering is the point of a gate.
Safeguard can enforce policies and gates on pull requests, and the free scan at gold.safeguard.sh/scan is a quick first look at your dependencies.
Quick check
Is it ever fine to use pull_request_target?
Yes, for jobs that only handle pull request metadata, such as labeling, as long as they do not check out or execute pull request code.
Does a label gate fully fix the risk?
No. The Security Lab article calls it a temporary fix because commits can be pushed after approval.
Sources
Last reviewed: 19 October 2026.