Why
The container boundary is good: no host mounts except the job tree and the
cache, --cap-drop ALL, no-new-privileges, no Docker socket, and no network
unless the job or the runner asks for one. What is not bounded is the resource
the job consumes:
--container-memory,--container-cpusand--container-pidsare optional. The deploy guide,packaging/runner.env, anddeploy/cloud-init/runner.yamldo not set them, so a job uses the runtime’s defaults, with no pids or memory limit./tmpis a tmpfs with no size, and the job tree is a read-write host bind mount with no quota. A job can fill the runner’s memory or disk before the runner removes the tree.
A contributor tip is approved by a maintainer, and a dependency can be compromised, so this is reachable by code the runner agrees to execute. The runner is a separate machine, so the forge’s database is not at risk; CI availability and the runner host are.
Scope
- Put finite
--container-memory,--container-pidsand a tmpfssize=in the documented runner configuration and the cloud-init file, with a note on choosing them for the machine’s size. - Decide how to bound the job tree: a filesystem quota, a maximum size checked while a job runs, or a separate filesystem. The cache and saved-checkout limits do not cover it.
- Consider a systemd backstop (
MemoryMax=,TasksMax=/LimitNPROC=) on the runner unit for the case where a job bypasses the runtime’s cgroup. - Add a test that the rendered runner arguments carry the limits, or that
container.RunArgsincludes them when configured.
Acceptance
- A runner created from the documented first-boot file has finite memory, pids, and tmpfs limits.
- The documentation says how the job tree is bounded, and a job cannot fill the runner disk unnoticed.
- A workflow still cannot remove or raise these limits; they remain the operator’s settings.
From the internal review for #106.