Safeguard
Developer Tools•

How do you scan pull requests from forks without exposing secrets?

Run fork code in an unprivileged job with no secrets, and never check out untrusted code under pull_request_target. Steps and a decision table.

Safeguard Security Team
4 min read

Run fork pull requests in an unprivileged job that has no secrets and read-only access, and keep any step that needs secrets in a separate job that never executes the fork's code. Avoid checking out untrusted fork code in a privileged workflow such as pull_request_target.

Why forks are a special case

On a public project, anyone can open a pull request from a fork. The code in that pull request is untrusted. A scan job that checks out and runs it with access to your secrets gives a stranger a path to those secrets.

GitHub's security hardening guide says this plainly. It states that the pull_request_target and workflow_run triggers, when used with the checkout of an untrusted pull request, expose the repository to compromise. These workflows are privileged: they may have repository write access and access to referenced secrets.

The same guide adds a point worth remembering: any user with write access to your repository has read access to all secrets configured there, so give workflows the least privilege they need.

Two jobs, two trust levels

The safe pattern separates what runs from what is trusted.

JobTrigger ideaSecretsRuns fork code?
ScanStandard pull request eventNoneYes, in isolation
ReportPrivileged event, after the scanOnly what it needsNo, it reads the scan output as data

The scan job analyzes the code and writes a result file. The report job reads that file, treats it as untrusted data, and posts a comment or status. It never builds, installs, or executes anything from the fork.

Steps

  1. Use the standard pull request trigger for the scan, so fork runs get a restricted token without secrets.
  2. Make the scan self-contained. If it needs vulnerability data, fetch public data and avoid private tokens.
  3. Upload the result as an artifact.
  4. In a separate workflow, download the artifact and validate its format before using it. Do not interpolate its content into shell commands.
  5. Set minimal permissions at the top of each workflow and raise them only per job.
  6. Require maintainer approval before running workflows for first-time contributors, where your platform supports it.

Mistakes that cause real incidents

  • Checking out the pull request head in a privileged workflow and then running a build or install step.
  • Using the pull request title or branch name inside a shell command without treating it as untrusted input.
  • Giving a scan job a broad token because it was convenient.
  • Caching dependencies in a way that lets an untrusted run poison a privileged run. The GitHub guide notes privileged workflows share the main branch cache.

What about private repositories?

If only your own team opens pull requests, the risk is lower, but insiders and compromised accounts exist. The same separation costs little and keeps the pattern consistent.

Where Safeguard fits

Safeguard can run its checks as gates on pull requests, and the same output can be consumed by a report job as data. Try a first scan at https://gold.safeguard.sh/scan.

Quick check

Is pull_request_target always unsafe?

No. It is safe when it does not check out or execute untrusted code. The danger is combining its privileges with fork code.

Should fork pull requests skip the scan?

Preferably not. Run an unprivileged scan on them, and keep secrets and posting steps in a separate trusted job.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.