# gitea-runner: workflow container.options passes host namespaces and capability flags to job container when privileged mode is disabled

act_runner passes workflow-controlled container.options into Docker HostConfig, letting a workflow author use --pid=host and --cap-add=ALL to run commands as root on the runner host.

- Payout: Research
- Severity: critical
- Program: gitea-runner
- Found by: sn0x-sharma
- Disclosed: 2026-10-02
- CVE: CVE-2026-73802
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-x4q3-gcj3-m6cf
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-x4q3-gcj3-m6cf
- Tags: area:cloud domain:infra impact:internal-access impact:privesc impact:rce kind:research platform:ghsa sev:critical tech:ci-cd tech:docker technique:source-review vuln:misconfiguration vuln:privilege-escalation vuln:rce

**Target:** CI runner job container (Docker HostConfig)
**Root cause:** Workflow YAML container.options are appended to runner-level options and parsed into HostConfig; when privileged mode is disabled only Privileged is forced false, and sanitizeConfig only strips Binds/Mounts, leaving PidMode, IpcMode, CapAdd and SecurityOpt intact.
**Steps:**
1. Submit workflow with `container.options: --pid=host --ipc=host --cap-add=ALL --security-opt seccomp=unconfined --security-opt apparmor=unconfined`
2. Runner merges these via mergeContainerConfigs into HostConfig with Privileged=false but host namespaces and caps preserved
3. Job container starts in host PID/IPC namespaces with all capabilities
4. Run `nsenter -t 1 -m -u -i -n -p -- sh -c "id > /tmp/marker"` to execute in host namespaces as root
**How found:** Source review of the act_runner container creation path: traced ContainerSpec.Options through RunContext.options() into mergeContainerConfigs and sanitizeConfig, noticing sanitizeConfig only filters Binds and Mounts while other dangerous HostConfig fields survive.
**Exploitation:** Attacker able to trigger a workflow on a shared Docker-backed act_runner supplies container.options; the job container is created with host PID/IPC namespaces and CAP_ALL, then nsenter into PID 1 namespaces yields root command execution on the runner host, exposing secrets and adjacent jobs.
**Escalation:** From host root access, pivot to runner host secrets, deployment credentials, adjacent jobs on the same runner, and internal build infrastructure.
**Insight:** When a CI runner gates 'privileged' by forcing only the Privileged boolean, audit every other HostConfig field that grants equivalent power (PidMode, IpcMode, CapAdd, SecurityOpt, Devices, VolumesFrom) — the sanitizer's allowlist scope is the real boundary.

