hostPath readOnly Split Leaves docker.sock Writable
A hostPath volume's readOnly flag lives independently from its volumeMount's readOnly flag in Kubernetes, and most admission policies only inspect the volume-level field. A pod can declare a read-only volume while mounting it writable, gaining full write access to /var/run/docker.sock and node root regardless of what the policy audit reports.
At a glance
- Unsafe setting
- hostPath.readOnly set to true while the corresponding volumeMounts.readOnly is left unset or false.
- Failure trigger
- An admission policy validates only the volume-level readOnly flag, admitting a pod whose volumeMount grants writable access to /var/run/docker.sock.
- Blast radius
- Writable docker.sock access lets a container spawn a privileged sibling container with the host root filesystem mounted, compromising every workload on that node.
- Recommended control
- Validate both readOnly fields together in admission policy, block hostPath sockets outright, and enforce PodSecurity restricted profile cluster-wide.
Fix commands and configuration
validate:n message: "volumeMounts.readOnly must be true when hostPath is mounted"n pattern:n spec:n containers:n - volumeMounts:n - =(mountPath): "/var/run/docker.sock"n readOnly: true/var/run/*.sockrestrictedThe Trap
The hostPath readOnly field split. Kubernetes exposes two separate readOnly booleans for a hostPath mount: one on spec.volumes[].hostPath.readOnly and another, entirely independent, on spec.containers[].volumeMounts[].readOnly. Setting the first to true does not constrain the second. Nothing in the PodSpec schema forces them to match.
The Default State
Most Kyverno and OPA Gatekeeper rules written to block docker.sock access inspect only hostPath.readOnly on the volume definition, because that field reads intuitively as “is this path writable”. Engineers writing manifests, and the policies auditing them, treat it as the single source of truth. The volumeMount’s own readOnly key defaults to false when omitted, and a policy that never checks it will pass a pod straight through even when the volume-level flag says true.
The Blast Radius
A pod ships with hostPath.readOnly: true against /var/run/docker.sock, satisfies the Kyverno ClusterPolicy that only checks that exact field, and gets admitted. The volumeMount omits readOnly entirely, defaulting to writable. Any process inside that container can now issue Docker Engine API calls over the socket: create a privileged container with the host filesystem bind-mounted at /, chroot into it, and read node-level kubelet credentials, kubeconfig files and any secrets cached in memory. The compromise reaches every workload scheduled on that node, and the admission log shows a clean pass, so incident responders start by ruling out the exact control that failed.
The Lead Mechanic Fix
Write policy rules that validate both fields independently and reject any mismatch, not just the volume-level flag. In Kyverno:
validate:n message: "volumeMounts.readOnly must be true when hostPath is mounted"n pattern:n spec:n containers:n - volumeMounts:n - =(mountPath): "/var/run/docker.sock"n readOnly: true
Pair this with a second rule denying any hostPath volume whose path matches /var/run/*.sock outright, since read-only socket access still permits enumeration. Enforce PodSecurity admission at the restricted profile cluster-wide via AdmissionConfiguration, which disallows hostPath volumes entirely rather than trusting per-field flags, and remove the exception namespace once workloads migrate to a CSI-backed runtime socket proxy with scoped RBAC instead of a raw Docker Engine bind mount.