Skip to content

CL-0011: Strong host-adjacent capabilities (NET_ADMIN, BPF, SYS_BOOT)

Severity: HIGH

Derivation (see severity model):

  • Baseline: A — the attacker already has code execution in this container, as the workload uid
  • Precondition: Technique — each member is a primitive that needs a published technique to become impact: ARP or route manipulation for NET_ADMIN, a BPF-based kernel attack, a reboot as a denial of service
  • Impact: Cross-container — traffic interception across containers on the host, kernel manipulation shared by all of them, or host availability
  • Qualifier/modifier: none
  • Derived: Technique × Cross-container = HIGH
  • Shipped: HIGH
  • Evidence: _cl0011 proves only that cap_add deposits the capability into the effective set — it does not exercise any member's reach. The cross-container impact (NET_ADMIN traffic manipulation on a shared Docker network, BPF kernel access) is reasoned from the capabilities' semantics under Docker's default seccomp profile; _t_net_admin covers only an in-netns route change, not cross-container interception. One member claim is empirically pinned: SYS_BOOT's kexec story is refuted, not assumed — kexec_load returns EPERM even with the capability held (captured on Docker 29.1.3), so the documented grant is reboot(2)

References: - OWASP Docker Security Rule #3 - CIS Docker Benchmark 5.4 — Ensure that Linux kernel capabilities are restricted within containers

What it detects

cap_add entries naming a capability that reaches past this container without handing over host code execution outright:

Capability Grants
NET_ADMIN reconfigure interfaces, routes and firewall rules — transparent interception of other containers' traffic, which NET_RAW alone cannot reach
BPF load BPF programs — kernel introspection and manipulation, and a recurring container-escape surface
SYS_BOOT reboot or power off the host via reboot(2). It does not load a kernel via kexec: kexec_load(2) returns EPERM even with the capability held (verified on Docker 29.1.3) — the default seccomp profile blocks it outright rather than gating it on a capability

CAP_-prefixed and lowercase spellings are matched the same way: Docker treats CAP_NET_ADMIN and NET_ADMIN as the same capability.

This rule is one of three over cap_add, split by what the capability grants, because SARIF advertises security-severity on the rule descriptor and a rule carrying two severities misreports one of them:

Rule Tier Members
CL-0024 CRITICAL ALL, SYS_ADMIN, SYS_MODULE, SYS_RAWIO
CL-0011 HIGH NET_ADMIN, BPF, SYS_BOOT
CL-0027 MEDIUM SYS_PTRACE, PERFMON, SYS_TIME, DAC_READ_SEARCH

Safe capabilities such as NET_BIND_SERVICE and CHOWN are not flagged by any of the three. Neither are MKNOD, SYS_CHROOT and DAC_OVERRIDE: all three are in Docker's default set, so flagging them on cap_add scored the declaration rather than the runtime state, and inverted the gate — cap_drop: [ALL] plus cap_add: [DAC_OVERRIDE] (one capability) failed at the default --fail-on high, while no cap_drop at all (fourteen capabilities, DAC_OVERRIDE among them) passed (issue #492).

Why it matters

Linux capabilities split root's privileges into discrete units, and these three are the ones that reach other containers or the host without being an escape on their own.

NET_ADMIN is the one to understand first, because it is the capability people add casually for VPN and networking containers. It is strictly more than NET_RAW (CL-0006): NET_RAW buys interception and denial of service against an L2 neighbour, while NET_ADMIN adds the routing and ip_forward control needed for a transparent man-in-the-middle that the victim cannot detect from its own connectivity.

BPF is a kernel read/write primitive on modern kernels and a recurring escape surface. SYS_BOOT is availability only — but host availability, which no other rule in the set reaches.

Fix

Remove the dangerous capability and evaluate whether the workload truly requires it:

# Before
services:
  app:
    image: myapp:1.0
    cap_add:
      - SYS_ADMIN

# After — drop all, add back only what's needed
services:
  app:
    image: myapp:1.0
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE

If a dangerous capability is genuinely required (e.g., SYS_PTRACE for a debugger sidecar), document the justification and consider running the workload outside a container.

When to suppress

  • NET_ADMIN for VPN/networking containers (Tailscale, WireGuard, OpenVPN) — required to manage the tun device and routing tables. Suppress with a reason: naming the network workload.
  • SYS_PTRACE for a debugger or profiler sidecar that you trust and that runs alongside the workload it inspects.

Do not suppress cap_add: ALL — there is no legitimate use case for granting every capability rather than enumerating the specific ones needed.

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
T1557 Adversary-in-the-Middle Credential Access (Enterprise/Linux, not on the Containers matrix)
T1040 Network Sniffing Credential Access (Enterprise/Linux, not on the Containers matrix)
T1529 System Shutdown/Reboot Impact

See also

  • CL-0002privileged: true (functional superset of cap_add: ALL)
  • CL-0006cap_drop: [ALL] baseline (drops the defaults this rule's cap_add re-grants)