Self-hosted runner security
The runner host is a trust boundary. A workflow can read or change anything its job environment can reach.
If a PR runs lint and tests on your hardware, treat that job as untrusted code. Keep the host small. Create one isolated environment per job. The environment registers for one job and disappears when the job ends.
Recommended model
Section titled “Recommended model”Use a just-in-time GitHub runner inside a rootless container. Just-in-time means GitHub registers the runner for one job only.
The environment should have:
- No host filesystem mounts
- No container engine socket
- No privileged mode
- No added Linux capabilities
no-new-privileges- CPU, memory, process, and disk limits
- An isolated working directory
- A short registration lifetime
- Automatic cleanup after success, failure, or timeout
The host keeps only the scheduler and container runtime. It must not keep project source or job credentials after cleanup.
Isolation options
Section titled “Isolation options”Default for normal repository checks. Fast and practical for a small organization. Shares the host kernel, so it is not a full hostile-code boundary.
Gives each job a separate kernel. Use when the host can support the added complexity.
Strongest common separation. Costs more time and operations work. Use for high-risk or untrusted workloads.
Removes the long-lived private host. Use when repository policy allows it. Do not switch only to avoid a queued designated runner.
Job admission
Section titled “Job admission”Run untrusted fork code without secrets. Do not use privileged workflow events to execute untrusted pull request code.
Limit GitHub token permissions per job. Keep package and cloud credentials out of validation jobs unless the check needs them.
Cleanup gate
Section titled “Cleanup gate”A job is complete only when:
- GitHub removes the one-job registration
- The isolated environment stops
- The work directory is gone
- Temporary credentials expire or are removed
- No project process remains on the host