When Does actions/checkout Enforce the Unsafe PR Checkout Guard?

The actions/checkout action enforces the unsafe PR checkout guard exclusively when a workflow runs on a pull_request_target or workflow_run event, the pull request originates from a fork repository, the checkout target explicitly references the fork's code, and the allow-unsafe-pr-checkout input remains unset or false.

The actions/checkout action implements this security mechanism to prevent "pwn-request" attacks, where malicious code from a fork could compromise repository secrets during CI execution. The guard operates as an opt-in protection system that blocks potentially dangerous checkouts unless explicitly disabled.

Conditions Required to Trigger the Guard

The guard is enforced only when all five specific conditions are satisfied simultaneously. If any condition fails, the checkout proceeds normally without security intervention.

1. Workflow Event Type

The guard activates exclusively for workflows triggered by pull_request_target or workflow_run events that were initiated by a pull request. The source code in src/unsafe-pr-checkout-helper.ts checks github.context.eventName against these specific event types to determine if protection is necessary.

2. Fork Origin Verification

The pull request must originate from a forked repository rather than a branch within the same repository. The system compares repository IDs using prHeadRepoId !== baseRepoId to confirm the head and base repositories differ.

3. Checkout Target Points to Fork Code

The action must detect that the checkout parameters explicitly target the forked pull request code. In src/unsafe-pr-checkout-helper.ts, the assertSafePrCheckout function performs three distinct validation checks:

  • repositoryMatchesPrHead: Performs a case-insensitive comparison between the input repository and the PR head repository name.
  • refMatchesPullPattern: Validates if the input ref matches the pattern refs/pull/<num>/<head|merge> using PR_REF_PATTERN.test(input.ref).
  • commitMatchesPrHeadSha: Checks if the supplied commit SHA exists within the PR head SHAs found in the event payload via prShas.includes(input.commit).

If any of these three conditions match, the checkout is considered to be targeting fork code.

4. No Explicit Opt-Out Provided

The user must not have set the allow-unsafe-pr-checkout input to true. When this input is enabled, the helper returns early and skips all security validations. This input is declared in action.yml and processed through the input helper in src/input-helper.ts.

5. Custom Ref or Commit Supplied

The guard only runs when the workflow explicitly supplies a custom ref or commit parameter. Default self-checkout operations (where no explicit target is specified) bypass the guard entirely. The invocation logic in src/input-helper.ts determines whether a custom checkout target exists before calling assertSafePrCheckout.

Source Code Implementation

The security logic resides primarily in src/unsafe-pr-checkout-helper.ts, which exports the assertSafePrCheckout function. This function throws an error with a clear security message when all dangerous conditions are met:


Refusing to check out fork pull request code from a 'pull_request_target' workflow…
To opt in, review the risks at https://gh.io/securely-using-pull_request_target
and set 'allow-unsafe-pr-checkout: true' on the actions/checkout step.

The src/input-helper.ts module prepares the IUnsafePrCheckoutInput interface and orchestrates the guard invocation, ensuring the security check occurs only after confirming a non-default checkout configuration.

Practical Configuration Examples

Guard Active: Blocked Checkout Attempt

When attempting to checkout fork PR code from a pull_request_target workflow without authorization, the guard blocks execution:

name: CI
on:
  pull_request_target:
    types: [opened, synchronize]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: refs/pull/${{ github.event.pull_request.number }}/head
      # Error: Refusing to check out fork pull request code...

Opt-In: Disabling the Guard

After reviewing the security advisory at https://gh.io/securely-using-pull_request_target, explicitly enable unsafe checkout:

name: CI
on:
  pull_request_target:
    types: [opened, synchronize]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: refs/pull/${{ github.event.pull_request.number }}/head
          allow-unsafe-pr-checkout: true

Safe Scenario: Guard Not Triggered

Standard events like push or pull_request (non-target) never trigger the guard, even for fork PRs:

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: refs/pull/${{ github.event.pull_request.number }}/head
      # Proceeds normally: event type does not require protection

Summary

  • The unsafe PR checkout guard protects against pwn-request vulnerabilities in pull_request_target and workflow_run workflows.
  • Enforcement requires five simultaneous conditions: specific event type, fork origin, explicit targeting of fork code, absence of the opt-out flag, and custom ref/commit parameters.
  • The protection is opt-out only; setting allow-unsafe-pr-checkout: true disables the security check for that step.
  • Core implementation resides in src/unsafe-pr-checkout-helper.ts with invocation logic in src/input-helper.ts.
  • The guard throws a descriptive error linking to GitHub's security advisory when dangerous checkout combinations are detected.

Frequently Asked Questions

What is the pwn-request vulnerability that this guard prevents?

The pwn-request vulnerability occurs when a pull_request_target workflow automatically checks out and executes code from a forked pull request, granting that code access to repository secrets and write permissions. Malicious actors can exploit this to exfiltrate secrets or modify repository code. The unsafe PR checkout guard prevents this by requiring explicit opt-in before allowing such checkouts.

How do I safely check out fork PR code for testing?

First, review the security risks documented at https://gh.io/securely-using-pull_request_target. Ensure your workflow does not execute untrusted code with elevated permissions. If absolutely necessary, set allow-unsafe-pr-checkout: true on the specific actions/checkout step, but isolate that step from any operations using sensitive secrets or write permissions.

Does this guard affect normal pull_request events?

No, the guard does not activate for standard pull_request events. It only enforces protection on pull_request_target and workflow_run events triggered by pull requests, as these contexts provide elevated repository permissions that make fork checkouts dangerous.

What happens if I don't specify a ref or commit in pull_request_target?

If you use actions/checkout@v4 without specifying a ref or commit input in a pull_request_target workflow, the action performs a default self-checkout of the base repository's default branch. Since this does not checkout the fork's code, the guard is not triggered, and the checkout succeeds without requiring allow-unsafe-pr-checkout.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →