Safeguard
Container Security

Containers Not Dropping Default Linux Capabilities

Docker grants every container 14 Linux capabilities by default. Here's why NET_RAW, SYS_CHROOT, and friends turn contained compromises into breakouts—and how to drop them safely.

Karan Patel
Cloud Security Engineer
7 min read

Most containers running in production today carry more power than the application inside them will ever use. When Docker or containerd starts a container without an explicit --cap-drop, the runtime hands it 14 Linux capabilities by default — a legacy default chosen for broad compatibility with 2014-era workloads, not for the microservices and multi-tenant clusters running in 2026. Capabilities like CAP_NET_RAW, CAP_SYS_CHROOT, and CAP_MKNOD sit unused in the vast majority of web services, APIs, and batch jobs, yet they remain available to any process inside the container that manages to execute code — including an attacker who lands a remote code execution bug in your application. Root-in-container is not equivalent to root-on-host, but retained capabilities are exactly the bridge that turns a contained compromise into a namespace escape, a network-level attack on sibling pods, or a full node takeover. This post breaks down what the default capability set actually contains, why it persists, and how to close the gap.

What are Linux capabilities, and why should container teams care?

Linux capabilities split the monolithic power of root (UID 0) into roughly 40 discrete privileges, introduced starting with Linux kernel 2.2 in 1999 specifically so that processes could get a sliver of root's power without getting all of it. As of kernel 6.x, there are 41 defined capabilities, ranging from CAP_CHOWN (change file ownership) to CAP_SYS_ADMIN (a notorious catch-all covering dozens of privileged syscalls). Containers matter here because a containerized process usually runs as UID 0 inside its own user namespace by default, and the container runtime decides which subset of those 41 capabilities that root user actually gets to exercise. Drop the wrong ones and legitimate functionality breaks (an nginx container binding to port 80 needs CAP_NET_BIND_SERVICE); leave the wrong ones in and you've handed an attacker tools they don't need. The entire point of "dropping default capabilities" is narrowing that gap between "root in name" and "root in practice" down to the exact set an application requires.

Which 14 capabilities does Docker grant every container by default?

Docker's default capability bounding set — inherited by most container runtimes that follow the OCI runtime spec, including containerd and CRI-O — is: CHOWN, DAC_OVERRIDE, FSETID, FOWNER, MKNOD, NET_RAW, SETGID, SETUID, SETFCAP, SETPCAP, NET_BIND_SERVICE, SYS_CHROOT, KILL, and AUDIT_WRITE. That's 14 out of 41 possible capabilities, granted to every container that doesn't explicitly opt out with --cap-drop. Kubernetes inherits this exact same default at the container runtime layer unless a Pod's securityContext.capabilities.drop field says otherwise — and as of 2026, the majority of Helm charts and public manifests we scan still don't set that field at all. A handful of these defaults are genuinely low-risk for most workloads (AUDIT_WRITE, SETFCAP), but several — NET_RAW, SYS_CHROOT, MKNOD — provide meaningful attack surface for workloads that never call the syscalls those capabilities gate.

Why is retaining CAP_NET_RAW risky in a shared cluster?

CAP_NET_RAW is risky because it lets a process open raw and packet sockets inside its network namespace, which is exactly what's needed to craft spoofed packets, run ARP poisoning against other pods sharing a CNI bridge, or sniff traffic that transits the same virtual interface. Almost no ordinary web service, database, or worker process calls socket(AF_PACKET, ...) — the capability exists by default purely for tools like ping and traceroute that need ICMP raw sockets, and most application containers never invoke either. In a Kubernetes cluster where multiple tenants' pods can land on the same node and share a bridge network, an attacker who achieves code execution in one container with NET_RAW intact can attempt to intercept or forge traffic belonging to a neighboring workload — a lateral-movement primitive that costs the attacker nothing extra because the capability was already sitting there by default. This is precisely why the Kubernetes Pod Security Standards "Restricted" profile — formalized in Kubernetes 1.23 (December 2021) and still the bar most compliance frameworks point to in 2026 — requires drop: ["ALL"] and only permits re-adding NET_BIND_SERVICE if a workload truly needs to bind a privileged port.

Which real-world breakouts have relied on retained capabilities?

Several of the most cited container escapes since 2019 depended on a capability the container was never supposed to keep. CVE-2022-0185, a heap buffer overflow in the Linux kernel's fs_context handling disclosed in January 2022, was exploitable specifically from inside containers that retained CAP_SYS_ADMIN (commonly present in containers running with --privileged or an overly broad custom capability set), letting an attacker escalate from container root to host root. CVE-2021-25741, patched in Kubernetes 1.22.3 / 1.21.6 / 1.20.12 (November 2021), let a pod's volume subPath manipulate host filesystem paths — an attack chain that becomes far more damaging when the exploiting container still holds CAP_DAC_OVERRIDE and CAP_CHOWN, both defaults, because they let it read and rewrite files it has no business touching. Even the widely referenced runc breakout, CVE-2019-5736 (February 2019), which let a malicious container overwrite the host runc binary via /proc/self/exe, was made materially easier by the fact that affected containers commonly ran with default capabilities intact rather than a minimized set. None of these bugs required --privileged mode — they required the ordinary, unremarkable default capability set that ships out of the box.

Does Kubernetes drop these capabilities for you automatically?

No — Kubernetes does not drop any default capabilities unless a manifest's securityContext explicitly says so, and out-of-the-box PodSecurity admission is often set to privileged or baseline rather than restricted. The baseline Pod Security Standard, the middle tier most clusters actually enforce, permits the full default 14-capability set plus explicitly listed additions like NET_RAW — it only blocks adding capabilities outside that already-permissive list. Only the restricted profile forces drop: ["ALL"] with narrow, explicit re-additions, and Kubernetes' own documentation as of the 1.30+ release cycle still describes restricted as opt-in, not the cluster default. In practice this means a fleet has to actively choose stricter Pod Security admission, or add per-workload capabilities.drop blocks, before any of this protection takes effect — leaving the vast majority of clusters running whatever the CRI shim's default happens to be.

How do you actually drop default capabilities without breaking workloads?

The reliable approach is drop: ["ALL"] at the top and re-add only what a strace or capability trace shows the process actually calling. In a Kubernetes Pod spec, that means:

securityContext:
  capabilities:
    drop: ["ALL"]
    add: ["NET_BIND_SERVICE"]  # only if binding <1024

For Docker or Compose, the equivalent is docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE. The hard part isn't the syntax — it's knowing which capability, if any, a given image's process genuinely exercises, because guessing wrong either leaves unnecessary privilege in place or breaks the application in a way that only surfaces at runtime (a crashed chown call, a failed bind, a setuid that silently no-ops). Tools like capsh --print, kernel audit logs filtered on capability-check events, or eBPF-based capability tracing during a staging run are the standard ways teams derive an accurate minimal set before enforcing it in CI.

How Safeguard Helps

Safeguard scans container images and running Kubernetes workloads to flag every container that hasn't dropped the default Linux capability set, ranking findings by exploitability rather than raw count — a container with NET_RAW and SYS_CHROOT intact next to a public-facing ingress gets prioritized above an internal batch job with the same defaults. Our policy engine ships pre-built rules mapped to the Kubernetes Pod Security Standards "restricted" profile and to CIS Kubernetes Benchmark control 5.2.8/5.2.9, so teams can enforce drop: ["ALL"] at admission time instead of discovering the gap during an audit. For images where the minimal capability set isn't obvious, Safeguard's runtime analysis observes actual syscall and capability usage across staging traffic and generates a recommended add: list per workload, turning what's normally a manual strace exercise into a one-click policy suggestion. And because capability drift tends to creep back in through copy-pasted manifests and forked Helm charts, Safeguard continuously re-scans deployed workloads and alerts when a new or updated container reintroduces a default capability that a prior policy had explicitly dropped — closing the loop between one-time hardening and ongoing supply chain security.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.