Join our Newsletter — 33% off our NHI Course
Home› Guides› How to Find GitHub Workflows Exposed to pull_request_target…
Guide Software & AI Supply Chain

How to Find GitHub Workflows Exposed to pull_request_target Abuse: Search and Fix

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 11 November 2026 12 min read
On this page

A pull_request_target workflow runs with the base repository's write token and secrets, so one that checks out or executes pull request code hands those privileges to anyone who can open a pull request. This page is the search-and-fix runbook for that one problem: find every workflow in an organisation that uses the trigger, sort the safe ones from the exposed ones, fix the exposed ones and keep new ones from appearing. It is for platform, DevOps and application security engineers who own GitHub organisations. Why privileged triggers are dangerous, and the wider set of workflow attacks, is covered in the GitHub and GitHub Actions Security Guide and the OWASP CI/CD Top 10 CICD-SEC-4 Poisoned Pipeline Execution Guide; this page focuses on doing the audit.

This is an independent guide from NHI Mgmt Group, based on GitHub's public documentation as of November 2026. It is not sponsored or reviewed by GitHub. Product features change, so confirm settings against GitHub's current documentation.

Key takeaways

  • The trigger alone is not the bug. GitHub's own guidance allows pull_request_target for jobs that label or comment; the exposure is the trigger combined with checking out, building or interpolating pull request content.
  • Search first, then triage by hand. Code search finds candidates in minutes; zizmor, OpenSSF Scorecard and CodeQL rank them; a person still has to read each flagged job.
  • The default fix is a split. Build and test untrusted code under pull_request with a read-only token, then do any privileged follow-up in a separate workflow_run job that treats artifacts as data.
  • The token outlives the checkout step. GitHub Security Lab notes that actions/checkout persists credentials by default, so later steps can read the token from disk; set persist-credentials: false.
  • Prevent regressions in CI. Run a workflow linter on every change under .github/workflows/ and treat a new privileged trigger as a reviewed change, not a routine edit.

When you need this

  • You own public repositories that accept fork pull requests. Anyone with a GitHub account can open a pull request, and GitHub's events reference states that pull_request_target workflows run in the context of the base repository's default branch.
  • You inherited workflows nobody has reviewed. Bots that label, triage, deploy previews or post coverage are the usual places this trigger appears.
  • A scanner flagged a "dangerous trigger" or "untrusted checkout". This page explains how to confirm whether the finding is real and how to fix it.
  • Private and internal repositories still count. The contributor pool is smaller, but an insider or a compromised contributor account can use the same path to reach deployment secrets.

What makes a workflow exposed

GitHub's secure use reference describes pull_request_target and workflow_run as privileged triggers that share the main branch's cache and says workflows using them "must not explicitly check out untrusted code, including from pull request forks". The events reference warns that running untrusted code on this trigger can lead to cache poisoning and to unintended access to write privileges or secrets. Read each candidate against this table.

Pattern in the workflowExposureWhy
Trigger only labels, comments or assigns using event metadata (PR number, author, changed file names via API)LowNo pull request code runs. Still restrict permissions to what the job needs.
actions/checkout with ref: ${{ github.event.pull_request.head.sha }}, head.ref or refs/pull/N/mergeCritical if any step then runs codeInstall hooks, build scripts, test runners and Makefiles from the pull request execute with the privileged token and secrets.
PR-controlled text (github.event.pull_request.title, .body, .head.ref) inside a run: blockCriticalTemplate expansion writes attacker text straight into the shell script, even without a checkout.
Label gate such as if: contains(github.event.pull_request.labels.*.name, 'safe to test') before a checkoutHighGitHub Security Lab calls labels a temporary measure: new commits can be pushed after a maintainer applies the label.
Writes to GITHUB_ENV or GITHUB_PATH from PR-derived valuesHighLater steps inherit attacker-chosen environment variables or binaries.
Cache save or restore in the privileged jobHighPrivileged triggers share the default branch cache, so a poisoned entry can reach release builds.
workflow_run job that executes downloaded artifactsCriticalGitHub Security Lab: artifacts produced from untrusted pull request data are themselves untrusted.

Note the December 2025 platform change. GitHub's changelog of 7 November 2025 announced that from 8 December 2025 the workflow file and checkout commit for pull_request_target always come from the default branch, so outdated copies on other branches no longer run. That narrows where to look, but it does not make an exposed workflow on the default branch safe.

Before you start

  • Access: read access to every repository in scope; organisation owner or security manager rights if you will enable code scanning or change Actions policies.
  • A token for tooling: zizmor reads a token from GH_TOKEN, GITHUB_TOKEN or ZIZMOR_GITHUB_TOKEN; per its docs, private repositories need contents: read (Actions), repo scope (classic PAT) or a fine-grained token with repository access. Use a short-lived token for the audit and revoke it afterwards.
  • A tracking sheet: repository, workflow path, trigger, job, finding, exposure rating, owner, fix status. This becomes the evidence trail.
  • Agreement on a freeze: owners of exposed workflows should be ready to disable them the same day if triage confirms a critical finding.

Step-by-step: find and triage

  1. Search the organisation with GitHub code search. Code search supports org:, path: globs, boolean operators and regular expressions. Start broad, then narrow:
    • org:YOUR-ORG path:.github/workflows pull_request_target lists every candidate file.
    • org:YOUR-ORG path:.github/workflows pull_request_target /head\.(sha|ref)/ narrows to files that also reference the pull request head.
    • org:YOUR-ORG path:.github/workflows workflow_run download-artifact finds privileged follow-up jobs that consume artifacts.
    • org:YOUR-ORG path:.github/workflows pull_request_target /github\.event\.pull_request\.(title|body|head\.ref)/ finds likely injection sinks.
    Add NOT is:archived to skip archived repositories, and record them separately. Code search does not support partial or regex matching on organisation names, so run one query per organisation.
  2. Run zizmor across the candidates. zizmor audits remote repositories by slug (for example owner/repo, optionally with @ref). Its dangerous-triggers audit flags pull_request_target, workflow_run and, from v1.31.0, issue_comment; template-injection flags ${{ }} expansions in code contexts; artipacked flags persisted checkout credentials; cache-poisoning flags cache use that can reach release workflows. Use --format=sarif to load results into code scanning, and note that the default "regular" persona favours high-signal findings while --persona=auditor shows everything for manual review.
  3. Cross-check with OpenSSF Scorecard. The Dangerous-Workflow check (risk Critical) looks for a pull_request_target or workflow_run trigger combined with an explicit checkout of pull request code, and for script injection with untrusted context variables. Scorecard's docs state it does not evaluate whether the checkout is guarded, for example by a label, so a Scorecard finding on a label-gated job still needs review. Its Token-Permissions check shows whether top-level permissions are read-only.
  4. Turn on CodeQL for workflows. GitHub's secure use reference says CodeQL can scan and detect potentially vulnerable Actions workflows. The CodeQL query help for Actions includes untrusted checkout queries for privileged and non-privileged contexts, plus TOCTOU variants, artifact poisoning, cache poisoning, code injection and excessive secrets exposure. Check that Actions appears in the language list when you configure default setup.
  5. Triage each hit by reading the job. For every flagged workflow answer: Does the job check out pull request content? Does any step after that execute files from the checkout (install, build, test, lint with plugins, scripts)? Which secrets, environments and cloud roles can the job reach? What does the permissions block grant? Rate it with the table above.
  6. Contain confirmed critical findings first. Disable the workflow (Actions tab, or rename the trigger in a protected change) before redesigning it. Then check the repository's Actions run history for runs from forks or first-time contributors and treat any secret the job could reach as exposed until proven otherwise. The CI/CD Pipeline Compromise Playbook covers the response if a run looks malicious.

How to fix each pattern

FindingFixNotes
Job builds or tests PR code under pull_request_targetMove it to pull_request; if it must comment or write status, add a separate workflow_run job that reads results from artifactsThe pattern GitHub Security Lab and the secure use reference recommend.
Job only labels or commentsKeep the trigger, remove any checkout, set job-level permissions to the minimum (for example pull-requests: write)Use the API for changed files rather than checking out the branch.
Untrusted text in run:Pass the value through env: and reference it as a shell variable, or use an action that takes it as an argumentzizmor's docs note ${{ env.VAR }} is still template expansion; use ${VAR} in the shell.
Label-gated checkoutReplace with the split above; if a gate must stay for now, check out the exact SHA that was reviewed and require environment approvalTreat as temporary, with an expiry date on the exception.
Persisted checkout credentialsSet persist-credentials: false on every checkout that does not pushzizmor's artipacked audit tracks this.
workflow_run consumes artifactsExtract to a temporary directory outside the workspace, read only simple values, validate them (for example that a PR number is an integer), never execute themPer GitHub Security Lab.
Cache use in privileged or release jobsRemove cache restore from release workflows, or make it read-only where the action supports itConfirm the exact input names in each action's documentation.

Set the workflow-level default to permissions: contents: read (or read-all) and grant writes per job, as both the secure use reference and Scorecard's Token-Permissions check recommend. Pin third-party actions used in privileged jobs to full commit SHAs; see How to Pin GitHub Actions to Commit SHAs.

Verify it worked

  • Re-run the same code search queries. Every remaining pull_request_target hit should be on your "low exposure, reviewed" list.
  • Re-run zizmor and Scorecard. Dangerous-Workflow should pass on fixed repositories; any remaining dangerous-triggers finding should carry a documented reason.
  • Test from a fork. Open a pull request from a test fork that changes a build script to print a marker. Confirm the marker appears only in the unprivileged pull_request run and that the run shows a read-only token and no secrets.
  • Close or rebase stale pull requests. GitHub Security Lab advises that open pull requests created before a fix may still run the vulnerable version; the December 2025 default-branch change reduces this, but confirm on your own runs.

Rollback and common errors

  • Rollback: revert the workflow change through a normal reviewed pull request. Never roll back to a version that checks out PR code in a privileged job; disable the automation instead and run it manually until fixed.
  • Environment protection stopped matching: since 8 December 2025, environment branch rules for pull_request events evaluate against refs/pull/N/merge and for pull_request_target against the default branch. Update filters rather than widening them to all branches.
  • Comment bot lost write access after the split: the workflow_run job needs its own permissions block; the pull_request job should stay read-only.
  • Annotations missing in CI: zizmor's docs note that a single step renders only 10 GitHub annotations; use SARIF upload for full results.

Prevent new exposures

  • Lint on change: run zizmor (SARIF) or rely on CodeQL for every pull request that touches .github/workflows/, and block merges on high findings.
  • Review ownership: add CODEOWNERS for .github/workflows/ so a platform or security reviewer approves trigger changes.
  • Fork run approvals: GitHub lets organisations require maintainer approval before workflows run on fork pull requests. Our recommendation (NHI Mgmt Group) is to require it for first-time and outside contributors, while not relying on it as the control for privileged triggers.
  • Least privilege for what the job can reach: keep deployment secrets in environments with required reviewers, and prefer OIDC federation over stored cloud keys so an exposed job cannot exfiltrate a long-lived credential. See the CI/CD Pipeline Identity Security Guide.

Measurement and framework mapping

Track these metrics, ours rather than GitHub's, monthly:

  • Workflows using pull_request_target or workflow_run, and the share reviewed and rated.
  • Open critical or high findings (untrusted checkout, injection) and median days to fix.
  • Repositories passing Scorecard Dangerous-Workflow and Token-Permissions.
  • Share of checkouts with persist-credentials: false.
  • Repositories with workflow linting enforced on pull requests.
FrameworkReferenceHow this task supports it
MITRE ATT&CK v20T1195.002 Compromise Software Supply ChainRemoves a route from a pull request to release secrets and artefacts.
NIST CSF 2.0ID.RA-01, PR.PS-06Identifies and records pipeline weaknesses; integrates secure practices into the development life cycle.
NIST SP 800-53 Rev 5AC-6, CM-6, SA-11Least privilege for workflow tokens, configuration settings for workflows, developer testing including static analysis of pipeline code.
OWASP Top 10 CI/CD Security RisksCICD-SEC-4 Poisoned Pipeline ExecutionThe risk class this audit addresses.

Practitioner checklist

  • Run the four code search queries for every organisation and record all hits, including archived repositories.
  • Scan candidates with zizmor and Scorecard; enable CodeQL for Actions where available.
  • Read every flagged job and rate it against the exposure table.
  • Disable critical workflows the same day and review fork run history and reachable secrets.
  • Move PR builds to pull_request and privileged follow-up to workflow_run that treats artifacts as data.
  • Route untrusted text through env:, set persist-credentials: false, and set read-only default permissions.
  • Remove cache use from release and privileged jobs.
  • Verify from a test fork and re-run all scanners.
  • Enforce workflow linting and CODEOWNERS on .github/workflows/.

Standards and references

Related NHI Mgmt Group resources: GitHub and GitHub Actions Security Guide · CI/CD Security Guide · OWASP CI/CD Top 10 CICD-SEC-4 Poisoned Pipeline Execution Guide · Self-hosted CI/CD Runners and Build Agents

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 11 November 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org