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.
# 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# 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.shReal-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
Data Hogo detects this vulnerability automatically.
Scan Your Repo FreeRelated Vulnerabilities
Unpinned GitHub Actions
highUsing GitHub Actions referenced by mutable tags like @main or @v3 instead of full commit SHAs means a compromised or hijacked action can inject malicious code into your CI pipeline without any change to your workflow file.
GitHub Actions Script Injection
criticalUsing untrusted event data like github.event.issue.title directly inside run: blocks allows attackers to inject arbitrary shell commands into your CI pipeline by crafting malicious issue titles, PR bodies, or commit messages.
Secrets Leaked in CI Logs
highPrinting or echoing environment variables containing secrets in CI scripts exposes them in build logs, which are often accessible to all repository collaborators and sometimes publicly visible on open-source projects.
Overly Permissive Workflow Permissions
mediumGitHub Actions workflows with permissions: write-all or no explicit permissions block grant the GITHUB_TOKEN excessive access, allowing a compromised step to modify code, create releases, write packages, or change repository settings.