Skip to main content

On This Page

Secure GitHub Actions: Implementing pull_request_target Without Supply Chain Risks

3 min read
Share

These articles are AI-generated summaries. Please check the original sources for full details.

pull_request_target Without Regret: Secure Fork PRs in GitHub Actions

GitHub Actions’ pull_request_target event provides write permissions and secret access to workflows triggered by external forks. Checking out and executing code from an untrusted fork’s SHA in this context creates a critical supply-chain vulnerability. This pattern allows attackers to exfiltrate tokens or abuse repository permissions via malicious scripts.

Why This Matters

In technical reality, maintainers often use pull_request_target to simplify CI automation for forks, but this inadvertently grants untrusted code access to the GITHUB_TOKEN and repository secrets. Ideal security models require a strict boundary between untrusted validation and privileged automation to prevent attackers from exfiltrating credentials. Failure to separate these contexts can lead to immediate supply-chain incidents where malicious contributors execute shell commands within a privileged repo context. Modern engineering requires architectural guardrails that enforce least-privilege permissions by default rather than relying on manual code review memory.

Key Insights

  • The pull_request_target event runs in the base repository context, granting elevated permissions that can be abused if fork code is executed (Buitelaar, 2026).
  • Separating workflows into Untrusted CI using pull_request and Trusted Triage using pull_request_target creates a necessary security boundary.
  • The workflow_run event serves as a secure boundary for privileged follow-up actions after untrusted checks pass safely.
  • Workflow Guardian is a tool used to automatically enforce security policies and catch dangerous patterns like unpinned external actions.
  • Explicit permission hardening, such as setting contents: read by default, prevents broad write permissions in untrusted workflows.

Working Examples

A dangerous example showing untrusted code execution inside a privileged context.

on: pull_request_target: types: [opened, synchronize] jobs: bad-example: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - run: npm ci && npm test

Recommended workflow for untrusted code execution with minimal permissions.

name: PR CI (untrusted) on: pull_request: types: [opened, synchronize, reopened] permissions: contents: read jobs: test: runs-on: ubuntu-latest steps: - name: Checkout PR code uses: actions/checkout@v4 - name: Set up Node uses: actions/setup-node@v4 - run: npm ci - run: npm test -- --ci

Practical Applications

  • System: GitHub repo triage. Use Case: Automated labeling for PR size using pr-size-labeler within a pull_request_target workflow restricted to metadata operations.
  • Pitfall: Executing npm install or npm test in a pull_request_target workflow, which allows a contributor to run arbitrary shell commands with repository secrets.
  • System: CI Reporting. Use Case: Posting CI summary comments via workflow_run to ensure the commenting bot only triggers after isolated test runs complete.
  • Pitfall: Leaving default GITHUB_TOKEN permissions as write-all instead of explicitly defining narrow scopes like contents: read in every workflow file.

References:

Continue reading

Next article

Safely Deploying ML Models to Production: Four Controlled Strategies

Related Content