Skip to main content

WTFork 🤌🤌

Pinning to SHA isn't the silver bullet you think it is.

This tool detects if your GitHub Actions are vulnerable to the Fork Network exploit (a.k.a. "The Invisible Network"), verifying that the commits you pin actually belong to the repositories you trust.

This project is inspired by this Aikido’s write-up on how fork networks work, and how Sai-Hulud was abusing it:

The Fork Awakens: Why GitHub’s invisible networks break package security


🚩 The Problem: "Invisible Networks"

Security best practices tell us to pin GitHub Actions to a full commit SHA (uses: owner/repo@<sha>). The assumption is simple: "If I pin the SHA, I trust code I'm running." Strict immutability usually equals strict trust

That assumption is wrong.

GitHub's architecture creates a loophole. It resolves SHAs across the entire fork network, not just the target repository.

This means uses: owner/repo@<sha> guarantees the code (the SHA), but it does not guarantee the source. A commit might resolve successfully from any fork, even if it never existed in the trusted owner/repo, effectively pulling unreviewed code from an obscure or malicious fork while appearing to come from upstream.

The Worst Case Scenario:

  1. Your workflow defines: uses: trusted-owner/action@<sha>
  2. That <sha> does not exist in trusted-owner/action.
  3. However, GitHub resolves it to a malicious commit in an obscure fork.
  4. You unknowingly execute untrusted-owner/action@<sha> from a different owner you never agreed to trust.

🛡️ How WTFork works

wtfork audits your repository’s GitHub Actions workflow files to validate that every referenced SHA is legitimate.

It scans:

  • .github/workflows/*.yml
  • .github/workflows/*.yaml

For every uses: owner/repo@<sha> found, wtfork performs a sanity check: Does this commit actually live inside owner/repo?

If the SHA is missing from the origin repo (but resolving due to the fork network quirk), wtfork flags it immediately so you can fix the pin before it becomes a supply-chain attack vector.

🚀 Usage

📦 Installation

Install wtfork directly from PyPI:

pip install wtfork

The most effective way to use wtfork is to run it automatically in your CI/CD pipeline. This blocks any Pull Request that introduces a vulnerable pin.

Create a file named .github/workflows/wtfork-scan.yml:

name: WTFork Security Scan 🤌

on: [push, pull_request]

jobs:
  wtfork_scan:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.10'

      - name: Install WTFork
        run: pip install wtfork

      - name: Run Scan
        run: wtfork
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

💻 Running Locally

To run wtfork locally, you must provide a GitHub Token (GITHUB_TOKEN) so the tool can query the API. The token needs repo scope (for private repos) or public access (for public repos).

1. Set the token:

  • Linux / macOS:
    export GITHUB_TOKEN=your_personal_access_token
    
  • Windows (PowerShell):
    $env:GITHUB_TOKEN="your_personal_access_token"
    

2. Run the tool:

# Run with default settings (scans .github/workflows against all refs)
wtfork

# Run on a specific directory
wtfork --path ./my-project/.github/workflows

⚙️ CLI Arguments & Configuration

wtfork [--path PATH] [--mode MODE]
Argument Default Description
--path .github/workflows Path to the directory containing your .yml or .yaml workflow files.
--mode all Comparison Strategy:
• default: Checks if the SHA exists in the repo's default branch (Fastest, but more relaxed).
• tags: Checks if the SHA belongs to any tag.
• branches: Checks if the SHA belongs to any branch.
• all: Checks everywhere (Safest, but slightly slower).

Examples:

# Fast check: Only allow SHAs that are present on the default branch (e.g., main/master)
wtfork --mode default

# Comprehensive check: Allow SHAs if they exist anywhere (tags or branches)
wtfork --mode all

License

MIT

Metadata

Release files for WTFork 1.0.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for WTFork 1.0.2
File Size Uploaded Provenance Actions
wtfork-1.0.2.tar.gz 5.1 kB Without provenance Details for wtfork-1.0.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for WTFork 1.0.2
File Interpreter ABI Platform Actions
wtfork-1.0.2-py3-none-any.whl Python 3 none any Details for wtfork-1.0.2-py3-none-any.whl

Total release size: 10.4 kB

Release files / wtfork-1.0.2.tar.gz

Download URL wtfork-1.0.2.tar.gz
Size 5.1 kB
Tags Source
SHA-256 checksum
How to use checksums
ddefca89633ab6e44df6368df4594939db662a227e7a043d8a7f561e9b2b178b
BLAKE2b-256 checksum
How to use checksums
69fca830ff011a519faa45f895792024512ed5620a3e2d1271684dbf6cf1a1e3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.7

Release files / wtfork-1.0.2-py3-none-any.whl

Download URL wtfork-1.0.2-py3-none-any.whl
Size 5.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a784480a9fc2a08e184057b9c37e3b9119f2206216c660929d48bfdd29d708a1
BLAKE2b-256 checksum
How to use checksums
5538ecef39db599f3dc5836f53bc1fb64593d5d84991bbedd0cee4ebcc75ccae
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.7

Security for WTFork 1.0.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.0.2 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page