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.
Operational summary
At a glance
- Symptom
- An admission policy validates only the volume-level readOnly flag, admitting a pod whose volumeMount grants writable access to /var/run/docker.sock.
- Likely cause
- hostPath.readOnly set to true while the corresponding volumeMounts.readOnly is left unset or false.
- Impact
- Writable docker.sock access lets a container spawn a privileged sibling container with the host root filesystem mounted, compromising every workload on that node.
- Verification signal
- Re-run the detection test and a controlled negative-path test.
- Safe correction
- Validate both readOnly fields together in admission policy, block hostPath sockets outright, and enforce PodSecurity restricted profile cluster-wide.
- Rollback or recovery
- Restore the exported configuration if the new control blocks required production traffic, then narrow the policy before redeployment.
The 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.
Apply the safer control
Before you change production
Confirm the affected scope, export the current configuration, and test the replacement control in a non-production environment first.
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/*.sockrestrictedVerify, roll back or escalate
Verify
Re-run the detection test and a controlled negative-path test. Confirm the unsafe behaviour is blocked while approved traffic still succeeds.
Rollback
Restore the exported configuration if the new control blocks required production traffic, then narrow the policy before redeployment.
Escalate
Escalate when the blast radius is uncertain, the control cannot be tested safely, or remediation requires an outage or security exception.