Skip to content

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-0006cap_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-0024cap_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.