Skip to main content
ISSUE-804 Critical Quick CLI Workflow triggers and permissions

pull_request_target workflow checks out the PR head

Control: pull_request_target must not check out the PR head · Config key: pullRequestTargetMustNotCheckoutHead

📋 What is this?

A workflow on the pull_request_target trigger explicitly checks out github.event.pull_request.head.sha (or head_ref). The job has access to the base repository's secrets *and* runs the PR author's code.

⚠️ Impact

This is the exact pattern behind the March 2025 tj-actions / reviewdog compromise. Any shell step that runs after the checkout is a direct path to secret exfiltration — npm install (runs package scripts), pytest (loads conftest.py), even cat README.md (via attacker-supplied content).

🔧 How to fix

Remove the explicit head checkout. pull_request_target should only check out the base branch (the default), where the code under review has already been merged.

✗ Before The PR author's code now runs with `pull_request_target`'s secret-bearing GITHUB_TOKEN.
# .github/workflows/lint.yml — ❌ Head checkout in privileged trigger
on: pull_request_target
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }} # ← attacker code
- run: ./lint.sh
✓ After `pull_request` runs without secret access on fork PRs — head checkout is safe.
# .github/workflows/lint.yml — ✅ Use pull_request, no head checkout needed
on: pull_request
permissions:
contents: read
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# default ref: github.event.pull_request.head.sha is fine on pull_request
- run: ./lint.sh

💡 Tips

  • If a workflow truly needs both PR code AND secrets, the right pattern is: PR workflow uploads diff artefact → trusted workflow_run consumes it (no checkout).
  • Plumber's default-on actionsMustBePinnedByCommitSha + ISSUE-802 + this rule cover the tj-actions class of vulnerabilities end-to-end.

⚙️ Configuration

This control is configured in .plumber.yaml under the github section:

github:
  controls:
    pullRequestTargetMustNotCheckoutHead:
      enabled: true

See the CLI documentation for the full configuration reference.