BlogCI/CD

Hardening self-hosted GitLab runners

Isolate jobs, scope CI tokens, and pin executors so one poisoned pipeline can't pivot into your whole GitLab runner fleet or leak another team's secrets.

Aug 13, 2024·10 min readAdvanced

A self-hosted runner executes whatever a pipeline tells it to — including code from a fork or a poisoned dependency. If runners are shared, privileged, or over-scoped, one malicious job owns your whole CI fleet. Isolation and least privilege are the whole game.

Shared-runner risk
Dangerous
privileged = true
shared across projects
wide-scoped tokens
reused build dirs
Contained
privileged = false
per-group runners
CI_JOB_TOKEN scoped
fresh workspace

Do not run privileged

Privileged runners exist mostly to build images with docker-in-docker. Use a rootless builder (Kaniko, Buildah) instead and keep the executor unprivileged.

config.toml
[[runners]]
executor = "docker"
[runners.docker]
privileged = false
volumes = ["/cache"] # never mount /var/run/docker.sock

Scope the tokens

A privileged runner is a host takeover
privileged = true plus a mounted Docker socket means any job can escape to the runner host and read every other project’s secrets. Treat it as equivalent to handing out root.

Use short-lived CI_JOB_TOKEN with the narrowest project scope, protect secret variables to protected branches, and give each team its own runner rather than one fleet for everything.

Go deeper in a courseSecure CI/CD with GitLabRunner isolation, scoped tokens, and protected pipelines.View course

Related posts