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:
_cl0011proves only thatcap_adddeposits the capability into the effective set — it does not exercise any member's reach. The cross-container impact (NET_ADMINtraffic manipulation on a shared Docker network,BPFkernel access) is reasoned from the capabilities' semantics under Docker's default seccomp profile;_t_net_admincovers 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_loadreturns EPERM even with the capability held (captured on Docker 29.1.3), so the documented grant isreboot(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_ADMINfor VPN/networking containers (Tailscale, WireGuard, OpenVPN) — required to manage the tun device and routing tables. Suppress with areason:naming the network workload.SYS_PTRACEfor 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 |