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 patternrefs/pull/<num>/<head|merge>usingPR_REF_PATTERN.test(input.ref).commitMatchesPrHeadSha: Checks if the supplied commit SHA exists within the PR head SHAs found in the event payload viaprShas.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 guardprotects against pwn-request vulnerabilities inpull_request_targetandworkflow_runworkflows. - 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: truedisables the security check for that step. - Core implementation resides in
src/unsafe-pr-checkout-helper.tswith invocation logic insrc/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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →