highCWE-94OWASP A08:2021

Self-Hosted Runner Risks

Using self-hosted GitHub Actions runners with pull_request_target or public fork workflows allows untrusted code from external contributors to execute on your infrastructure with access to secrets, persisted state, and the host network.

How It Works

Self-hosted runners are machines you manage that execute GitHub Actions workflows. Unlike GitHub-hosted runners, they are not ephemeral -- they persist between runs, retaining files, credentials, and network access. When a workflow uses pull_request_target, it runs in the context of the base repository (with access to secrets) even when triggered by a pull request from a fork. An attacker forks the repo, modifies the workflow or build scripts to include malicious code, and opens a PR. The workflow runs the attacker's code on your self-hosted runner with full access to repository secrets, the host filesystem, and your internal network. The attacker can install backdoors, steal credentials, or pivot to other systems on the same network.

Vulnerable Code
# BAD: self-hosted runner with pull_request_target = RCE from forks
name: PR Build
on:
  pull_request_target:  # runs in base repo context with secrets!
    types: [opened, synchronize]
jobs:
  build:
    runs-on: self-hosted  # persistent machine, not ephemeral
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.sha }}  # checks out FORK code
      - run: npm install  # executes fork's package.json scripts on YOUR machine
      - run: npm test     # fork's test files run with access to secrets + network
Secure Code
# GOOD: use GitHub-hosted runners for untrusted code, approve fork workflows
name: PR Build
on:
  pull_request:  # NOT pull_request_target -- limited context for forks
jobs:
  build:
    runs-on: ubuntu-latest  # ephemeral GitHub-hosted runner
    steps:
      - uses: actions/checkout@v4
      - run: npm install
      - run: npm test
  # If you MUST use self-hosted runners:
  deploy:
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    runs-on: self-hosted  # only for trusted pushes to main
    needs: build
    steps:
      - uses: actions/checkout@v4
      - run: ./deploy.sh

Real-World Example

In 2021, GitHub security researcher Teddy Katz disclosed vulnerabilities in major open-source projects (including several CNCF projects) where self-hosted runners were used with pull_request_target workflows. Attackers from forks could execute arbitrary code on the project's infrastructure. GitHub subsequently added a 'Require approval for all outside collaborators' setting and strongly recommended against using self-hosted runners for public repositories.

How to Prevent It

  • Never use self-hosted runners for workflows triggered by pull_request_target or pull_request from public repositories with external contributors
  • Use ephemeral GitHub-hosted runners for all CI builds that process untrusted code from forks and PRs
  • If self-hosted runners are required, use ephemeral container-based runners (actions-runner-controller with ephemeral pods) that are destroyed after each job
  • Enable 'Require approval for all outside collaborators' in repository settings to manually approve fork workflow runs

Affected Technologies

GitHub ActionsNode.jsPythonDocker

Data Hogo detects this vulnerability automatically.

Scan Your Repo Free

Related Vulnerabilities