When a workflow uses pull_request or pull_request_target triggers and runs on self-hosted runners, any fork contributor can execute arbitrary code on your infrastructure. Unlike GitHub-hosted runners, self-hosted runners are not ephemeral -- they persist between runs, sharing filesystems, credentials, and network access.
An attacker forks the repo, modifies workflow files or test scripts to include malicious commands, and opens a PR. The self-hosted runner executes their code with access to:
Use GitHub-hosted runners for workflows that respond to fork PRs:
# Before (vulnerable)
on: pull_request
jobs:
test:
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- run: npm test
# After (safe)
on: pull_request
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
If you must use self-hosted runners, gate the trigger to only run on maintainer-applied labels:
on:
pull_request:
types: [labeled]
Self-hosted runners are persistent infrastructure. A compromised runner can attack your internal network, steal credentials from other projects, or establish long-term persistence.