Skip to content

Security

The runner mounts /var/run/docker.sock. That is root on the host.

Any workflow this runner executes can start a privileged container and from there read or write anything on the machine: the other stacks, their volumes, their secrets, the host filesystem. There is no sandbox, and none is intended — the design assumes you trust every workflow in the repository you point it at.

“Every workflow” is broader than it sounds. It means every workflow file, on every branch, written by anyone who can push to the repository or open a pull request against it.

situationwhy it breaksinstead
the repository is public, or accepts pull requests from forksa stranger’s pull request runs as root on your hostdon’t. If you must: RUNNER_EPHEMERAL=true, a throwaway VM, and read GitHub’s own warning
RUNNER_SCOPE=orgthe runner accepts jobs from every repository in the organisation, including ones somebody adds laterscope it to one repository
the host runs production or another project’s dataone runaway job reaches itgive the runner its own machine

GitHub’s own documentation puts it briefly and correctly: “We recommend that you only use self-hosted runners with private repositories.” The reason they give is the first row of that table — a fork’s pull request can execute code on your runner.

This is the wrong tool, and that is not a criticism of either side. Use:

A half-sandbox that reads as safe is worse than an honest warning, which is why this stack does not pretend to offer one.

Use a specific label. With only self-hosted, any job in scope lands on this runner — including jobs the host cannot serve, which then fail in confusing ways. RUNNER_LABELS=e2e and runs-on: [self-hosted, linux, x64, e2e].

Keep the registration token out of everything. It is single-use and expires in an hour, so it is nearly worthless after the deploy — but there is no reason for it to sit in a repository, a chat log or an issue. Portainer’s environment block is the right place.

Opt out of your host’s auto-updater. Both services carry com.centurylinklabs.watchtower.enable=false. Many hosts run a Watchtower that watches every container by default, and one that restarts the runner mid-job loses the job and reports it as cancelled. Any service you add to this stack needs the same label.

Bump the runner image deliberately. It is pinned, because a runner that pulls a new image mid-job loses the job — which also means you do not get the runner’s security updates automatically. GitHub force-upgrades the runner binary when it falls far enough behind, but do not rely on that as your update strategy.

A private security advisory, not a public issue. The repository’s SECURITY.md lists what is in scope — and it explicitly includes documentation that understates a risk.