Skip to main content
RunBook Academy

Git, CI/CD & GitOpsCIV · CI/CD Anti-PatternsRunnerHardening

Permanent privileged runners — long-lived self-hosted runners with standing credentials

Intermediate⏱ ~22 mingit

What you'll learn

  • Identify the three standing-privilege shapes that turn a self-hosted runner into a persistence host
  • Explain why a runner that never restarts is a runner that never re-checks trust
  • Distinguish ephemeral runners with OIDC federation from long-lived runners with stored credentials

Prerequisites

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

A permanent self-hosted runner is a host an attacker inherits - always on, registered, holding a long-lived cloud key, and answering every PR the forge sends. The first untrusted PR that runs sudo has not broken out of a sandbox; it has landed on a host whose privileges are the runner’s.

Three shapes the violation takes

  • Always-on VM. Linux host registered 24/7 with sudo and a stale ~/.aws/credentials.
  • Shared Docker socket. /var/run/docker.sock mounted into every job; docker run --privileged escapes to the host as root.
  • Persistent cloud key. AWS_ACCESS_KEY_ID exported from a startup script and reused across every job.
    flowchart LR
        A["Always-on runner VM"] --> D["Standing privilege"]
        B["Mounted Docker socket"] --> D
        C["Persistent cloud key"] --> D
        D --> E["Compromised PR pivots to host"]
        D --> F["Compromised PR pivots to cloud"]
        D --> G["Credentials reused across jobs"]
        D --> H["No job-to-job isolation"]

Why a runner that never restarts never re-checks trust

Ephemeral runners flip the model: the host boots with no credentials; the job requests an OIDC token (short-lived, scoped to repo and branch); the host terminates; the token is revoked. A permanent runner skips every step, and the ~/.aws/credentials from the previous job stays on disk.

What ephemeral runners with OIDC look like

Ephemeral runners. Actions Runner Controller scales a Kubernetes pod per job; --ephemeral runners register, take one job, and unregister. OIDC federation. The cloud never sees a long-lived key; it sees a token issued by the forge, scoped to the repository and branch, with a sub claim naming the workflow. The trust policy in cloud IAM decides whether the token may assume a role.

Production discipline

  1. Runners are ephemeral by default.
  2. Standing credentials are forbidden on runner hosts. ~/.aws/credentials is minted per job by OIDC.
  3. The Docker socket is not mounted.

Cross-course references

  • This course, Part XLI (RunnerSecurity) - runner threat model, Docker socket risk.
  • This course, Part XLIII (OIDC) - OIDC federation.
  • Linux for Production Sysadmins, Part XII (RepoSecurity) - package repository trust.

Quiz

Knowledge check · 4 questions

  1. Q1. A self-hosted runner registered two years ago holds an AWS access key in ~/.aws/credentials. A fork PR exfiltrates it. What has the runner leaked?

  2. Q2. Mounting the host Docker socket into a CI job container is not necessarily acceptable just because the job runs untrusted code in a sandbox.

  3. Q3. Name the two controls that together remove the standing credential from a runner host.

  4. Q4. Diagnose a permanent-runner compromise and recommend the architecture that prevents it.

    Terraform pipeline runs on a runner registered eighteen months ago holding ~/.aws/credentials, mounting /var/run/docker.sock into every job, never rebooted.

Passing score: 75%. Answers are checked in this browser.