# How actions/checkout Prevents Pwn Request Attacks in pull_request_target Workflows

> actions/checkout stops pwn request attacks in pull_request_target workflows. Learn how it secures your base repository secrets from malicious fork pull requests.

- Repository: [GitHub Actions/checkout](https://github.com/actions/checkout)
- Tags: security
- Published: 2026-07-16

---

**The `actions/checkout` action blocks checkout of fork pull request code in privileged `pull_request_target` workflows unless explicitly enabled via `allow-unsafe-pr-checkout`, preventing attackers from exfiltrating base repository secrets.**

The `actions/checkout` action implements a critical security guard to mitigate "pwn request" vulnerabilities when handling the `pull_request_target` event. This event executes workflows with the base repository's `GITHUB_TOKEN`, secrets, and full runner access, creating a high-risk attack surface if malicious fork code is allowed to run. By default, the action refuses to check out code from forked pull requests in this privileged context, requiring explicit opt-in only after security review.

## Understanding the Pwn Request Vulnerability

A "pwn request" attack occurs when a malicious actor submits a pull request from a forked repository containing exploitable code. When a workflow triggers on `pull_request_target`, GitHub executes that workflow with the **base repository's permissions**—including access to secrets, the `GITHUB_TOKEN` with write permissions, and the default branch cache scope.

If the workflow checks out and executes code from the forked PR in this privileged context, the attacker can exfiltrate secrets, modify the base repository, or compromise the runner. This vulnerability specifically affects `pull_request_target` and `workflow_run` events because they run with elevated privileges compared to standard `pull_request` events.

## The Safety Guard: assertSafePrCheckout

The core protection logic resides in [`src/unsafe-pr-checkout-helper.ts`](https://github.com/actions/checkout/blob/main/src/unsafe-pr-checkout-helper.ts) within the `assertSafePrCheckout` function. This guard implements an eight-step validation process that halts execution when it detects potentially dangerous checkout attempts.

### Step 1: Opt-Out Validation

The guard first checks for explicit user consent to bypass security checks.

```typescript
if (input.allowUnsafePrCheckout) return

```

If the `allow-unsafe-pr-checkout` input is set to `true`, the function returns immediately, permitting the checkout to proceed. This allows advanced users to accept the risk after reviewing the security implications.

### Step 2: Event Filtering

The protection only applies to high-risk workflow triggers.

```typescript
const eventName = github.context.eventName
if (eventName !== 'pull_request_target' && eventName !== 'workflow_run') return

```

Standard `pull_request` events run with the fork's own limited token, so they do not require this guard. The check specifically targets `pull_request_target` and `workflow_run` events that execute with base repository privileges.

### Step 3: Repository Identification

The function extracts repository identifiers from the GitHub webhook payload to distinguish between the base repository and potential forks.

```typescript
const baseRepoId = fromPayload('repository.id')
// ...
prHeadRepoId = fromPayload('pull_request.head.repo.id')
prHeadRepoFullName = fromPayload('pull_request.head.repo.full_name')

```

This retrieves the base repository ID and the fork's repository ID for comparison.

### Step 4: Fork Detection

The guard determines if the PR originates from a fork by comparing repository IDs.

```typescript
if (typeof prHeadRepoId !== 'number' || prHeadRepoId === baseRepoId) return

```

If the head repository ID matches the base repository ID, the PR originates from an internal branch rather than a fork, making the checkout safe.

### Step 5: Intent Verification

Before blocking, the guard verifies whether the checkout request actually targets the fork's code through three validation checks:

```typescript
const repositoryMatchesPrHead =
  typeof prHeadRepoFullName === 'string' &&
  input.qualifiedRepository.toLowerCase() === prHeadRepoFullName.toLowerCase()
const refMatchesPullPattern = PR_REF_PATTERN.test(input.ref)
const commitMatchesPrHeadSha = !!input.commit && prShas.includes(input.commit.toLowerCase())

```

- **Repository match**: The checkout targets the fork's repository name specifically
- **Ref pattern match**: The ref follows the pull request pattern (`refs/pull/...`)
- **SHA match**: The commit SHA exists in the fork's head or merge commit SHAs

If none of these conditions match, the function returns without error, assuming the checkout targets unrelated code.

### Step 6: Security Exception

When the guard detects a fork PR checkout attempt without explicit opt-in, it throws a descriptive error:

```typescript
throw new Error(
  `Refusing to check out fork pull request code from a '${eventName}' workflow. ` +
  `This workflow runs with the base repository's GITHUB_TOKEN, secrets, default-branch ` +
  `cache scope, and runner access. Fetching and executing a fork's code in that trusted ` +
  `context commonly leads to "pwn request" vulnerabilities. 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.`
)

```

This error message references the official GitHub security guide and explains the specific risks involved.

## Configuration and Usage

### Default Safe Behavior

By default, workflows using `pull_request_target` will fail when attempting to checkout fork PRs:

```yaml

# .github/workflows/pr-target.yml

on:
  pull_request_target:
    types: [opened, synchronize]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - run: echo "This runs only for internal PRs"

```

When triggered by a forked PR, this configuration produces the security error and halts execution.

### Explicit Opt-In for Unsafe Checkout

After reviewing the security risks at `https://gh.io/securely-using-pull_request_target`, enable fork PR checkout with explicit consent:

```yaml
on:
  pull_request_target:
    types: [opened, synchronize]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with:
          allow-unsafe-pr-checkout: true
      - run: echo "Now we can run on fork PRs"

```

### Safe Checkout Using Verified Fork SHA

Checkout specific fork commits without enabling unsafe mode by referencing the exact SHA from the event payload:

```yaml
- uses: actions/checkout@v7
  with:
    ref: ${{ github.event.pull_request.head.sha }}

```

This satisfies the guard's intent verification because the SHA matches the fork's head commit extracted from the webhook payload.

## Key Implementation Files

The pwn request protection spans several files in the `actions/checkout` repository:

| File | Purpose |
|------|---------|
| [`src/unsafe-pr-checkout-helper.ts`](https://github.com/actions/checkout/blob/main/src/unsafe-pr-checkout-helper.ts) | Implements the `assertSafePrCheckout` function containing the security logic |
| [`src/main.ts`](https://github.com/actions/checkout/blob/main/src/main.ts) | Invokes the safety guard before executing the actual Git checkout operations |
| [`action.yml`](https://github.com/actions/checkout/blob/main/action.yml) | Declares the `allow-unsafe-pr-checkout` input parameter and its default value of `false` |
| [`README.md`](https://github.com/actions/checkout/blob/main/README.md) | Documents the security behavior and links to the `pull_request_target` security guide |

## Summary

- **Default protection**: `actions/checkout` automatically refuses to check out fork PR code in `pull_request_target` and `workflow_run` workflows to prevent secret exfiltration.
- **Explicit opt-in**: The `allow-unsafe-pr-checkout` input bypasses the guard only after user acknowledgment of the security risks.
- **Smart validation**: The guard verifies repository IDs, ref patterns, and commit SHAs to distinguish between intentional fork checkouts and potentially malicious attempts.
- **Clear error messages**: Failed attempts provide specific guidance linking to GitHub's secure usage documentation.
- **Source location**: The protection logic resides in [`src/unsafe-pr-checkout-helper.ts`](https://github.com/actions/checkout/blob/main/src/unsafe-pr-checkout-helper.ts) and is invoked from [`src/main.ts`](https://github.com/actions/checkout/blob/main/src/main.ts).

## Frequently Asked Questions

### What is a pwn request attack?

A pwn request attack occurs when a malicious actor submits a pull request from a forked repository that exploits the privileged context of `pull_request_target` workflows. Because these workflows run with the base repository's `GITHUB_TOKEN` and secrets, executing untrusted fork code allows attackers to steal secrets, modify the repository, or compromise the CI runner.

### Why does pull_request_target create security risks?

The `pull_request_target` event runs workflows in the context of the **base repository** rather than the fork, granting access to sensitive credentials and write permissions that standard `pull_request` events do not have. If the workflow checks out and runs code from the forked PR, it executes untrusted code with trusted privileges, creating the "pwn request" vulnerability.

### How can I safely checkout fork PRs without enabling unsafe mode?

Use the specific commit SHA from the pull request event payload: `ref: ${{ github.event.pull_request.head.sha }}`. This satisfies the guard's intent verification because the SHA matches the fork's head commit identified in the webhook payload, proving the checkout specifically targets the fork's code rather than tricking the workflow into checking out unrelated malicious code.

### What happens if I don't set allow-unsafe-pr-checkout?

Without `allow-unsafe-pr-checkout: true`, the workflow fails with an error message explaining the pwn request risk when `actions/checkout` detects a fork PR checkout attempt in a `pull_request_target` workflow. The error directs you to GitHub's security documentation and explains that the workflow runs with the base repository's secrets and token.