Skip to content

SEC003 — dangerous-trigger-checkout

Severity: blocker · Category: Security

What it checks

Flags workflows triggered by pull_request_target, workflow_run, or issue_comment (events that run with elevated GITHUB_TOKEN privileges and access to repo secrets, but can be invoked by anyone without repo write access) that also actions/checkout an untrusted head — a ref: pointing at github.event.pull_request.head.*, github.head_ref, github.event.workflow_run, or a ChatOps-style refs/pull/<issue number>/head.

Why it matters

pull_request_target deliberately runs in the base repo's context (so it has secrets and write access), but its default checkout is the base branch — safe. The vulnerability only appears when a workflow explicitly overrides that to check out the PR author's code instead: now their build scripts, test fixtures, or even a malicious commit run with privileges they were never supposed to have. This is the single most-exploited GitHub Actions misconfiguration pattern.

Examples

Flagged — checks out the PR's own head commit while running with base-repo privileges:

on: pull_request_target

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.2.2
        with:
          ref: ${{ github.event.pull_request.head.sha }}
      - run: npm ci && npm test # runs the PR author's code with your secrets

Fixed — either drop the ref: override (so it checks out the safe base branch), or, if the workflow genuinely needs to build PR code, split it into an unprivileged pull_request-triggered job that does the building and a separate, minimal pull_request_target job that only reads metadata (never checks out code):

on: pull_request_target

jobs:
  read-metadata-only:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: read
    steps:
      - run: echo "PR title is ${{ github.event.pull_request.title }}"
        # no checkout at all — nothing here can execute PR-supplied code

Suppressing

The finding is reported on the uses: actions/checkout@... line itself:

      - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # vlotpipe: ignore[SEC003]
        with:
          ref: ${{ github.event.pull_request.head.sha }}

Only if you've verified — and ideally documented right there in a comment — exactly why the checkout is safe (e.g. later steps only read a specific, non-executable file path and never run PR-supplied code).