hostPath Policy Prefix Checks Miss the /run Symlink
An admission policy written against /var/run/docker.sock does nothing if the node's /var/run is a symlink to /run, because a pod can mount /run/docker.sock directly and the same socket becomes reachable under a path string the policy never inspected. Namespace-scoped exemptions and standard RBAC reviews will not catch this on Kubernetes nodes running systemd-based distributions.
Operational summary
At a glance
- Symptom
- A pod mounts hostPath.path: /run/docker.sock, bypassing a policy written against /var/run/docker.sock, and gains the container runtime socket unchecked.
- Likely cause
- Admission policy allowedHostPaths matches only the literal /var/run/docker.sock string without resolving the /var/run to /run symlink.
- Impact
- The pod obtains root-equivalent access to the node runtime, reads every co-located pod's secrets, and pivots across the entire node.
- Verification signal
- Re-run the detection test and a controlled negative-path test.
- Safe correction
- Enumerate all canonical path aliases in policy or, preferably, block hostPath volumes entirely via the restricted PodSecurity Standard.
- Rollback or recovery
- Restore the exported configuration if the new control blocks required production traffic, then narrow the policy before redeployment.
The Trap
Host path prefix matching that treats /var/run and /run as unrelated strings. Most systemd-managed Linux distributions ship /var/run as a symbolic link to /run, created by tmpfiles.d at boot. Kernel-level path resolution treats /var/run/docker.sock and /run/docker.sock as the identical inode, but a Kubernetes admission controller performing string-prefix comparison on volumes[].hostPath.path sees two unrelated values. Any policy authored solely against /var/run/docker.sock leaves /run/docker.sock, and by extension /run/containerd/containerd.sock, entirely unchecked.
The Default State
Kyverno’s community restrict-host-path-mount policy, and most hand-rolled OPA Gatekeeper ConstraintTemplates, ship with an allowedHostPaths pathPrefix list containing literal strings such as /var/run/secrets or /var/run/docker.sock. Neither Kyverno nor Gatekeeper resolves symlinks before evaluating the pattern; the JMESPath or rego comparison operates on the raw string supplied in the PodSpec. kubelet itself performs no canonicalisation either, so a container requesting hostPath.path: /run/docker.sock is admitted and bind-mounted exactly as written, regardless of what the same file is called elsewhere on disk.
The Blast Radius
A pod that passes every existing hostPath guardrail mounts /run/docker.sock, obtains a working Docker Engine API socket, and issues a privileged container create against the host runtime. From there the attacker gains root on the node, reads kubelet’s client certificate and any other pod’s secrets mounted on that node, and pivots laterally through every workload co-located on the same host. Security teams who audited their cluster and confirmed docker.sock was blocked under /var/run remain unaware that the exact same capability was reachable one directory alias away, and standard policy-as-code test suites that only assert against the documented path never exercise the bypass.
The Lead Mechanic Fix
Enumerate every canonical alias explicitly rather than trusting a single prefix. In Kyverno, replace a single pathPrefix entry with an anyPattern block listing both forms:
spec.validationFailureAction: Enforce, with rule.validate.anyPattern covering path: “/var/run/docker.sock” and path: “/run/docker.sock”, plus the equivalent pair for containerd.sock and crictl’s runtime endpoint. Better still, remove hostPath as an option entirely by setting pod-security.kubernetes.io/enforce: restricted at namespace level, since the restricted PodSecurity Standard rejects the hostPath volume type outright rather than trying to enumerate forbidden paths. Where a workload genuinely needs runtime introspection, replace the raw socket mount with a scoped CSI ephemeral volume or a sidecar exposed via a Unix domain socket proxy that enforces its own authorisation, so no policy has to keep pace with the node’s filesystem layout.
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.
Enumerate all canonical path aliases in policy or, preferably, block hostPath volumes entirely via the restricted PodSecurity Standard.
Validate the vendor-specific syntax in official documentation before applying it.
Verify, 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.