How actions/checkout Prevents Ref Parsing Attacks with ASCII-Only Trimming

actions/checkout prevents ref parsing attacks by explicitly trimming only ASCII whitespace characters from the ref input while preserving Unicode characters like BOM or NBSP, ensuring malicious branch names cannot be disguised as commit SHAs to bypass unsafe fork PR protections.

The actions/checkout repository implements a critical security control to prevent attackers from bypassing branch protections through maliciously crafted ref names. By deliberately avoiding the default Unicode-aware input trimming provided by @actions/core, the action ensures that invisible Unicode characters remain part of the ref string during validation. This ASCII-only trimming technique blocks a specific attack vector where malicious branch names containing leading Byte Order Marks (BOM) or non-breaking spaces could otherwise be interpreted as raw commit SHAs.

The Vulnerability: Unicode Trimming and Git Ref Confusion

By default, core.getInput() performs aggressive Unicode whitespace trimming that strips characters like the Byte Order Mark (BOM, U+FEFF) and Non-Breaking Space (NBSP, U+00A0). These characters are valid within Git ref names but are invisible to human reviewers. An attacker could craft a malicious branch name consisting of a BOM followed by a 40-character hexadecimal SHA (e.g., <BOM>deadbeef...). If the default trimming were applied, the BOM would be removed, leaving only the SHA string. This transformed input would be interpreted as a commit hash rather than a branch reference, bypassing the unsafe fork PR protections that only monitor custom branch checkouts.

ASCII-Only Trimming Implementation

Bypassing Default Core Input Trimming

In src/input-helper.ts, the action explicitly disables automatic whitespace trimming when reading the ref input parameter. Instead of using the default behavior of core.getInput(), which strips all Unicode whitespace, the code passes trimWhitespace: false to preserve the raw input string including any Unicode prefixes.

The Regex Pattern and Security Boundary

After retrieving the raw input, the action applies a strict ASCII-only regular expression to remove only characters that are explicitly forbidden in Git ref names: tabs, newlines, vertical tabs, form feeds, carriage returns, and spaces. The implementation at lines 62-78 of src/input-helper.ts uses the following pattern:

const asciiTrimmedRef = core
  .getInput('ref', {trimWhitespace: false})
  .replace(/^[\t\n\v\f\r ]+|[\t\n\v\f\r ]+$/g, '')

This regex limits trimming to the specific ASCII characters \t \n \v \f \r SPACE, ensuring that any Unicode whitespace or control characters remain attached to the ref string.

How the Protection Works

Preserving Unicode Attack Vectors

By limiting trimming to ASCII characters, the action ensures that Unicode whitespace characters like BOM (U+FEFF) remain attached to the ref string. This preservation is intentional—these characters are valid in Git refs but would dangerously alter the interpretation if stripped. When a malicious actor attempts to pass <BOM>+40-hex-sha, the BOM remains intact after the ASCII-only trim, preventing the string from being misidentified as a pure SHA.

SHA Validation and Ref Routing

Following the trimming operation, the processed ref is evaluated in src/ref-helper.ts (lines 22-33). If the string matches a 40- or 64-character hexadecimal SHA pattern, it is treated as a commit hash and assigned directly (result.commit = asciiTrimmedRef). Otherwise, it proceeds as a branch or tag reference. Because Unicode prefixes remain intact due to ASCII-only trimming, a malicious ref containing a BOM fails the SHA regex match and routes through the safer branch validation path rather than the commit-only path.

Unsafe Fork PR Guard Integration

The src/unsafe-pr-checkout-helper.ts module implements the critical safety check that prevents checkout of arbitrary refs from fork pull requests. According to the source code at lines 13-22, this protection is only invoked when the ref is not identified as a commit SHA. By ensuring that Unicode-prefixed strings do not match the SHA pattern, the ASCII-only trim forces these potentially malicious inputs through the unsafe PR helper, which blocks the checkout unless allow-unsafe-pr-checkout: true is explicitly set in the workflow configuration.

Attack Scenario and Mitigation

Consider a workflow that receives a malicious ref input containing a leading BOM:

- uses: actions/checkout@v4
  with:
    repository: myorg/myrepo
    ref: "\uFEFFdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"   # BOM + 40-hex SHA

The action processes this input through the following security boundary:

  1. core.getInput('ref', {trimWhitespace: false}) returns the raw string including the BOM character.
  2. The ASCII-only regex removes only spaces, tabs, newlines, and other ASCII whitespace; the BOM remains attached to the string.
  3. The resulting string does not match the SHA-only regex due to the leading BOM, so it is treated as a ref rather than a commit.
  4. The unsafe PR guard in src/unsafe-pr-checkout-helper.ts detects a custom ref checkout from a fork and throws a security error unless allow-unsafe-pr-checkout: true is explicitly configured.

Summary

  • ASCII-only trimming in src/input-helper.ts removes only \t \n \v \f \r SPACE while preserving Unicode characters that are valid in Git refs.
  • By disabling trimWhitespace in core.getInput(), the action prevents the accidental stripping of BOM and NBSP characters that could mask malicious SHA patterns.
  • The ref routing logic in src/ref-helper.ts distinguishes between commit SHAs and branch refs based on the preserved string content.
  • The unsafe fork PR protection in src/unsafe-pr-checkout-helper.ts reliably intercepts attacks because Unicode-prefixed refs are routed through branch validation rather than treated as trusted commit hashes.

Frequently Asked Questions

What characters does actions/checkout trim from the ref input?

The action trims only ASCII whitespace characters: space ( ), tab (\t), newline (\n), vertical tab (\v), form feed (\f), and carriage return (\r). This is implemented in src/input-helper.ts using the regex /^[\t\n\v\f\r ]+|[\t\n\v\f\r ]+$/g.

Why is preserving Unicode whitespace important for security?

Unicode characters like BOM (U+FEFF) and NBSP (U+00A0) are valid in Git ref names but invalid in commit SHAs. If these were stripped by default trimming, an attacker could create a branch named with a BOM prefix followed by a SHA string. After trimming, this would appear as a legitimate SHA, bypassing branch-based security checks and potentially allowing unsafe fork PR checkouts without explicit opt-in.

How does the unsafe fork PR protection interact with ref parsing?

The unsafe fork PR protection in src/unsafe-pr-checkout-helper.ts (lines 13-22) validates that custom refs from fork pull requests are not checked out unless allow-unsafe-pr-checkout: true is set. Because ASCII-only trimming preserves Unicode prefixes that prevent SHA matching, malicious refs are forced through this guard rather than being treated as commit SHAs that would skip the security check.

Where is the ASCII-only trimming logic implemented?

The implementation resides in src/input-helper.ts at lines 62-78, where the code calls core.getInput('ref', {trimWhitespace: false}) followed by the ASCII-only regex replacement. The validation logic that depends on this trimming is located in src/ref-helper.ts (lines 22-33) and the security guard is in src/unsafe-pr-checkout-helper.ts (lines 13-22).

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 →