CL-0002: privileged: true — what it grants and how to replace it¶
Severity: CRITICAL
Derivation (see severity model):
- Baseline: A — the attacker already has code execution in this container, as the workload uid
- Precondition: Direct — the container starts with every capability, every host device, and no default seccomp or AppArmor profile
- Impact: Host
- Qualifier/modifier: none
- Derived: Direct × Host = CRITICAL
- Shipped: CRITICAL
- Evidence:
_cl0002— a privileged container holds the full capability set, verified against a live daemon on every CI run
References: - OWASP Docker Security Rule #3 - CIS Docker Benchmark 5.5 — Ensure that privileged containers are not used
What it detects¶
Any service with privileged: true.
Why it matters¶
Privileged mode disables nearly all container isolation. The container receives every Linux capability, every host device under /dev, and runs without the default seccomp and AppArmor profiles. PID, mount, and network namespaces are still in place by default, but escape to host root is trivial — mount, unshare, mknod, and BPF are all unblocked, and the host's block devices are all present, so mounting the host root filesystem (mount /dev/sda1 /mnt) is a one-liner.
The release_agent escape that older write-ups lead with is cgroup v1 only. Current hosts run the v2 unified hierarchy, which has no release_agent file (verified: CgroupVersion: 2 on the daemon these rules are grounded against). Do not treat its absence as evidence that a privileged container is contained — the block-device path above needs no cgroup trick at all.
Treat privileged: true as trivially escapable to host root, not literally equivalent to running on the host — but the practical attack distance is one well-known technique away.
Fix¶
privileged: true is almost never the right tool. The legitimate use cases (Docker-in-Docker, GPU passthrough, kernel module loading) each have surgical alternatives:
| Use case | Surgical alternative |
|---|---|
| Mount filesystems | cap_add: SYS_ADMIN (still dangerous; consider whether the mount can happen on the host) |
| Specific device access | devices: [/dev/foo] + device_cgroup_rules |
| Network configuration | cap_add: NET_ADMIN + cap_add: NET_RAW |
| Bind privileged ports | cap_add: NET_BIND_SERVICE |
| Run a debugger sidecar | cap_add: SYS_PTRACE (note: also allows reading any process's memory — see CL-0011) |
| Docker-in-Docker | Prefer sysbox-runc or rootless DinD over --privileged |
Replace it with a least-privilege configuration:
# Instead of:
privileged: true
# Use:
cap_drop:
- ALL
cap_add:
- NET_ADMIN # only what you actually need
security_opt:
- no-new-privileges:true
If you don't know which capabilities are needed, remove privileged: true, run the service, and add back only the specific capabilities the workload demands.
ATT&CK coverage¶
Remediating this finding contributes to mitigating the following MITRE ATT&CK techniques (pinned to ATT&CK v18). compose-lint is a static analyser, so this is mitigation coverage — it detects nothing at runtime.
| Technique | Tactic |
|---|---|
| T1611 Escape to Host | Privilege Escalation |
| T1685 Disable or Modify Tools | Defense Impairment |
See also¶
- CL-0006 —
cap_drop: [ALL](privileged grants all caps) - CL-0009 — seccomp / AppArmor (privileged drops both default profiles)
- CL-0010 — host namespaces (privileged does not by itself share them, but is often combined with them)
- CL-0024 —
cap_add: [ALL]is the cap-only subset of privileged - CL-0011 / CL-0027 — the other capability tiers
When CL-0002 fires, expect CL-0006, CL-0009, and often CL-0010/CL-0024 to fire on the same service.