Once code runs in a job, it has everything the job has: every secret in scope, the runner's file system, its network reach and whatever later jobs leave on a reused runner. Scoping each pipeline is what limits a compromise to one job.
- Do not share runners between pipelines of different sensitivity, and return each runner to a clean state after every job.
- Scope secrets and token permissions to the pipeline and step that need them.
- Run jobs as a least-privileged OS user, apart from the CI controller, on patched nodes.
- Limit each runner's network access to what its jobs need, including outbound internet access.
Guides on this site
Harden GitHub Actions against workflow and supply-chain attacks
Close the paths that give untrusted pull-request code secrets or a write token: pass context through env, keep pull_request_target away from fork code, pin actions to commit SHAs, scope GITHUB_TOKEN and OIDC, and treat artifacts as data.
Keep untrusted CI work outside the release boundary
Review workflow authority, persistent state and artifact handoffs before contributor-controlled code reaches privileged jobs.
Case study: the Codecov Bash Uploader compromise (2021)
How a credential leaked through Codecov's Docker image process let an attacker change the Bash Uploader that many CI pipelines downloaded and ran, which sent their environment variables, and the secrets in them, to the attacker for two months in 2021.
Practice
Gaps on this site
The GitHub Actions guides cover per-job permissions, ephemeral runners and runner groups. No guide here walks through isolating self-hosted runners and restricting their network egress, or covers runners on other CI systems.
References
List data from the publisher. A guide is listed as the main subject when the weakness or risk is what the guide is about, and as covering it when a dedicated section or worked example addresses it.