# How Does Actions/Checkout Protect Against Checking Out Untrusted Forks: Security Architecture Explained

> Discover how actions/checkout secures your CI/CD pipelines by preventing untrusted fork checkouts. Learn about its three-stage fork-checkout guard architecture.

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

---

**Actions/checkout prevents malicious code execution from untrusted forks by enforcing a three-stage fork-checkout guard that blocks checkouts originating from forked pull requests when workflows run in privileged contexts like `pull_request_target` or `workflow_run`.**

The `actions/checkout` GitHub Action implements sophisticated security measures to safeguard your CI/CD pipeline from "pwn request" attacks. Understanding how does actions/checkout protect against checking out untrusted forks is essential when handling external contributions, as these protections prevent exfiltration of secrets and unauthorized access to runner infrastructure. This analysis examines the specific implementation details found in the `actions/checkout` source code.

## The Fork-Checkout Guard Architecture

According to the `actions/checkout` source code, the protection mechanism operates through a multi-layered validation system defined in [`src/unsafe-pr-checkout-helper.ts`](https://github.com/actions/checkout/blob/main/src/unsafe-pr-checkout-helper.ts). The guard implements three distinct detection stages to identify and block potentially dangerous checkout operations.

### Stage 1: Detecting Privileged Workflow Events

The guard first determines whether the workflow executes in a high-risk context. In `unsafe-pr-checkout-helper.ts:L18‑L21`, the code checks if the triggering event is `pull_request_target` or `workflow_run`. These events run with the base repository's `GITHUB_TOKEN`, secrets, and runner access, making them valuable targets for attackers seeking to compromise your infrastructure through malicious fork code.

### Stage 2: Identifying Forked Pull Requests

Once a privileged context is confirmed, the system compares repository identifiers to detect forks. The logic in `unsafe-pr-checkout-helper.ts:L52‑L55` evaluates the base workflow's `repository.id` against the pull request's head repository ID (`pull_request.head.repo.id` or `workflow_run.head_repository.id`). When these numeric IDs differ, the system identifies that the PR originates from a fork rather than the trusted base repository.

### Stage 3: Validating Requested Refs and Commits

The final validation layer ensures the requested checkout target actually points to the untrusted fork code. The implementation in `unsafe-pr-checkout-helper.ts:L60‑L70` executes three specific verification functions:

- **`repositoryMatchesPrHead`** – Verifies if the target repository matches the PR head repository
- **`refMatchesPullPattern`** – Validates if the ref follows the conventional pattern `refs/pull/<num>/<head|merge>`
- **`commitMatchesPrHeadSha`** – Confirms whether the commit SHA matches the PR head SHA

If any condition evaluates to true, the checkout is rejected with a detailed security error message defined in `unsafe-pr-checkout-helper.ts:L74‑L80` that explains the risk and references the opt-in documentation.

## Default Self-Checkout Bypass

The fork-checkout guard is intentionally bypassed for default self-checkout scenarios. As implemented in `src/input-helper.ts:L88‑L93`, when users leave the `repository` and `ref` inputs empty, the action trusts the ref and commit supplied directly by GitHub's infrastructure. This safe default ensures legitimate workflows continue operating without friction while maintaining protection against explicit overrides that could introduce untrusted code.

## Opting In to Unsafe Fork Checkouts

For advanced use cases requiring explicit fork code access in privileged contexts, `actions/checkout` provides a controlled escape hatch. The `allow-unsafe-pr-checkout` input, parsed in `src/input-helper.ts:L82‑L86`, disables the security guard when set to `true`. In `unsafe-pr-checkout-helper.ts:L14‑L16`, this flag causes the validation logic to skip safety checks entirely, permitting the checkout to proceed.

```yaml

# ❌ Blocked: Attempting explicit fork checkout in pull_request_target

- uses: actions/checkout@v4
  with:
    repository: attacker/fork
    ref: refs/pull/42/head

# ✅ Allowed: Explicit opt-in with security acknowledgment

- uses: actions/checkout@v4
  with:
    repository: attacker/fork
    ref: refs/pull/42/head
    allow-unsafe-pr-checkout: true

```

## Summary

- **Event-aware protection**: The guard activates exclusively for high-risk `pull_request_target` and `workflow_run` events that grant access to base repository secrets.
- **Repository ID verification**: Fork detection relies on comparing numeric repository IDs between the base and head repositories in `unsafe-pr-checkout-helper.ts:L52‑L55`.
- **Ref/commit validation**: Three specific checks (`repositoryMatchesPrHead`, `refMatchesPullPattern`, `commitMatchesPrHeadSha`) ensure the checkout target belongs to the fork before blocking.
- **Safe defaults**: Default self-checkout operations bypass the guard since GitHub supplies trusted refs directly, as noted in `input-helper.ts:L88‑L93`.
- **Explicit opt-in required**: Users must consciously set `allow-unsafe-pr-checkout: true` to override security protections, ensuring acknowledged risk acceptance.

## Frequently Asked Questions

### What triggers the fork-checkout protection in actions/checkout?

The protection triggers when a workflow runs in a privileged context—specifically `pull_request_target` or `workflow_run` events—and the user explicitly specifies a fork repository or PR ref from a fork. The system validates these conditions in `unsafe-pr-checkout-helper.ts:L18‑L21` and compares repository IDs in `unsafe-pr-checkout-helper.ts:L52‑L55` to detect the fork condition before checking whether the requested ref points to untrusted code.

### Can I checkout code from a fork in a pull_request_target workflow?

By default, no. The action blocks explicit fork checkouts in `pull_request_target` workflows to prevent "pwn request" attacks where malicious code could access your repository secrets with elevated permissions. You must set `allow-unsafe-pr-checkout: true` to override this protection, as implemented in `input-helper.ts:L82‑L86` and processed in `unsafe-pr-checkout-helper.ts:L14‑L16`.

### Why does the default checkout work without triggering the security guard?

Default self-checkout (leaving `repository` and `ref` inputs empty) bypasses the guard because GitHub's infrastructure supplies the ref and commit directly from trusted sources. As implemented in `input-helper.ts:L88‑L93`, this ensures standard workflows run efficiently while only custom checkouts with explicit overrides undergo the full fork validation sequence.

### What specific checks validate whether a ref belongs to a fork?

The action performs three specific validations in `unsafe-pr-checkout-helper.ts:L60‑L70`: `repositoryMatchesPrHead` verifies the target repository matches the PR head, `refMatchesPullPattern` checks if the ref follows `refs/pull/<num>/<head|merge>`, and `commitMatchesPrHeadSha` confirms if the commit matches the PR head SHA. If any check passes, the checkout is blocked with an explanatory error.