They can, if your workflow is written carelessly. GitHub warns that the pull_request_target and workflow_run triggers, combined with checking out an untrusted pull request, expose a repository to compromise because those workflows are privileged. Keep untrusted code in unprivileged workflows with no secrets, and put privileged steps behind review.
The pattern to avoid
Pull request workflows normally run with limited permissions when the change comes from a fork. Two triggers behave differently. GitHub's security guidance says they are privileged, which means they share the same cache as the main branch with other privileged triggers, and may have repository write access and access to referenced secrets.
The dangerous combination is a privileged trigger plus a step that checks out and runs code from the pull request. The pull request author, who might be a contractor, a stranger or an attacker holding a contractor's account, then controls code that runs with secrets in scope. GitHub states these weaknesses can be used to take over a repository.
A safe split of work
| Stage | Trigger type | Secrets | Runs untrusted code |
|---|---|---|---|
| Build and test the change | Unprivileged pull request trigger | None | Yes |
| Publish results or comment | Privileged follow up workflow | Only what it needs | No, it reads artifacts as data only |
| Deploy | After merge, on a protected branch, in a protected environment | Environment secrets | No |
GitHub recommends avoiding the privileged triggers unless the workflow needs that context. For separating privilege between workflows, it describes workflow_run as the better trigger, and it says to treat artifacts uploaded by other workflows with caution.
Other points from GitHub's guidance
- Least privilege for the token. Set the default GITHUB_TOKEN permission to read only for repository contents and raise it per job.
- Pin third party actions to a full-length commit SHA, so a moved tag cannot change what runs.
- Be careful with self-hosted runners. GitHub notes that anyone who can fork a repository and open a pull request, generally those with read access, can compromise a self-hosted runner environment on private or internal repositories too, including reaching secrets and the token. Public repositories should almost never use them.
- Treat untrusted input as untrusted. Pull request titles, branch names and comments flowing into a shell command are a script injection path.
- Protect workflow files with CODEOWNERS so edits need a designated reviewer. See protecting workflow files from outside contributors.
Why contractors raise the stakes
Contractor accounts have Write on private repositories more often than random strangers do, and Write already reads repository secrets, as GitHub's guidance notes. That means the fork specific danger is only part of the picture. The same discipline protects against a contractor branch that edits a workflow. Move high value secrets into protected environments so write access alone is not enough.
An audit checklist
- Search your workflows for pull_request_target and workflow_run. Write down why each is used.
- For each, check whether any step checks out the pull request head and runs it.
- Confirm no secret is passed to a job that runs pull request code.
- Confirm the default token is read only.
- Confirm actions are pinned.
- Confirm workflow files have a code owner.
Safeguard's policies and gates, dependency scanning and asset discovery can support the surrounding checks on pull requests and help you find pipelines you forgot. It does not remove the need to design workflows safely. Run the free scan.
Quick check
Is pull_request_target always unsafe?
No, but GitHub advises avoiding it unless you need the privileged context, and never using it to check out and run untrusted pull request code.
Do self-hosted runners help?
They can make it worse. GitHub warns that anyone who can fork and open a pull request can compromise a self-hosted runner environment on private repos too.
Sources
Last reviewed: 19 October 2026.