BlogKubernetes

How container escapes happen — and how to prevent them

Privileged flags, host mounts, and leaked sockets are the usual doors. Close them before an attacker finds them.

Jan 28, 2025·4 min readAdvanced·By the SecOpsLog team · command-tested

Containers are not virtual machines. They are ordinary processes on a shared kernel, separated by namespaces and cgroups — and every misconfiguration is a hole in that separation. Nearly every real-world container escape in production traces back to the same few doors: a privileged pod, a hostPath mount of / or /var/run/docker.sock, excessive Linux capabilities like CAP_SYS_ADMIN, or a workload running as root with a writable host mount. Close those doors and you have eliminated most of what attackers actually use — not theoretical CVEs, but the config someone copied from a Stack Overflow answer.

Defense is layered: admission policy blocks dangerous pod specs before they schedule, Pod Security Standards enforce baseline constraints at the namespace level, and runtime tools like Falco catch syscall patterns that policy missed. The Runtime security track walks through escape mechanics and the detection rules that prove your hardening worked.

Escape doors on a shared kernel

privileged, hostPath, and missing seccomp are the usual doors. Close them with PSS.

Host kernel (shared) — escape target privileged or CAP_SYS_ADMIN hostPID / hostNetwork door #1 hostPath / /var/run/docker.sock write the host FS door #2 No seccomp or AppArmor/SELinux wide syscall surface door #3 Defenses PSS restricted drop caps · readOnly no host mounts One privileged pod with docker.sock is a host root foothold. Assume breakout is possible — shrink the blast radius with PSS + network policy + runtime detection. shared kernel + misconfig → escape → host close doors before you detect them
privileged — capshostPath — sockDefend — PSS
Escape prevention stack

One privileged DaemonSet with a host mount undoes every other control. Audit cluster-wide, not namespace by namespace.

1Inventoryprivileged + hostPath pods2PSA enforcerestricted where possible3Admissiondeny hostPath, privileged4Drop capsALL + add minimal5runAsNonRootfixed UID in image6seccompRuntimeDefault profile7Falcodetect mount/ns escape

What an escape looks like

With --privileged, the container sees nearly all host devices and capabilities. Mount the host root filesystem and chroot into it — you are now root on the node, with access to every other pod's data and the kubelet credentials on disk. This is not a sophisticated exploit; it is three flags and one command.

escape-demo.sh (do NOT run in prod)
# --privileged + host mount = trivial host takeover
docker run --rm --privileged -v /:/host alpine \
chroot /host sh -c "id && cat /etc/shadow | head -1"
# you are now root on the node filesystem

The usual doors — and how to shut them

The usual doors
Wide open
securityContext.privileged: true
hostPath: / or /var/lib/kubelet
docker.sock mount
runAsUser: 0, no seccomp
CAP_SYS_ADMIN added
Shut
privileged: false (default)
no hostPath in app pods
never mount docker.sock
runAsNonRoot: true
capabilities.drop: [ALL]
The Docker socket is root on the host
Mounting /var/run/docker.sock into a container is equivalent to giving it root on the node — it can start a privileged sibling container at will. CI runners and log agents are the usual offenders. Use rootless builders (Kaniko, BuildKit) instead of docker-in-docker with a socket mount.

A hardened pod spec

Apply the same constraints in Docker and Kubernetes. Read-only root filesystem where possible, all capabilities dropped, no privilege escalation, seccomp profile set, and a non-zero UID baked into the image — not just declared in the manifest while the image still runs as root.

deploy-hardened.yaml
securityContext:
runAsNonRoot: true
runAsUser: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]

Detect escapes you did not prevent

Policy drift happens. Falco rules on mount to sensitive paths, unexpected setns, or execution from /proc/self/exe after a breakout attempt catch what admission missed. Route alerts to the same channel as GuardDuty findings — an escape attempt on a node is a paging event, not a dashboard tile. Re-run your privileged-pod inventory after every platform upgrade; DaemonSets and monitoring agents are how escaped configs creep back in.

bash — find risky pods todaylive
kubectl get pods -A -o json | jq -r '.items[] | select(.spec.containers[]?.securityContext?.privileged==true) | .metadata.namespace + "/" + .metadata.name'
kube-system/node-agent privileged=true
kubectl get pods -A -o json | jq -r '.items[] | select(.spec.volumes[]?.hostPath) | .metadata.namespace + "/" + .metadata.name'
every hostPath mount deserves a written justification

Where this goes next

Blocking escapes at admission is necessary; detecting them at runtime is how you know the blocks held. Next: seccomp and AppArmor profiles tuned to your workloads, gVisor or Kata for high-risk multi-tenant isolation, and regular red-team exercises against your cluster baseline. The Runtime security path covers escape techniques, Falco tuning, and incident response on compromised nodes.

Go deeper in a courseRuntime securityContainer escapes, seccomp, Falco detection, and runtime incident response.View course

Related posts