This check flags GKE clusters that run without pod-level security controls, which lets pods request privileged access, host mounts, and root execution. PodSecurityPolicy is deprecated and removed in Kubernetes 1.25, so the modern fix is to enable Pod Security Admission with a baseline or restricted standard, or deploy a policy engine like Gatekeeper.
If a workload in your GKE cluster gets compromised, the next question an attacker asks is simple: how far can this pod reach? Without pod-level security controls, the answer is often "much further than it should." A container that can run as root, mount the host filesystem, or share the host network turns a single application bug into a node-wide, sometimes cluster-wide, problem.
This Lensix check, gke_podsecuritypolicy, looks at your GKE clusters and reports the ones that have no pod security policy in place. Below we cover what it actually detects, why it matters, and how to fix it properly given that the original PodSecurityPolicy feature is now gone.
What this check detects
The check inspects your GKE clusters for an enabled pod admission control policy. Historically this meant the PodSecurityPolicy (PSP) admission controller, a Kubernetes feature that gated pod creation based on the security context a pod requested. If a pod asked for something the policy did not allow, such as privileged mode, the API server rejected it.
A cluster flagged by this check has no such guardrail. Any user or service account with permission to create pods can schedule a workload with whatever security context they want, including configurations that should never run in production.
Note: PodSecurityPolicy was deprecated in Kubernetes 1.21 and fully removed in 1.25. GKE stopped supporting PSP on newer clusters accordingly. If you are reading this in the context of a modern cluster, treat "configure pod security policy" as "configure a pod security mechanism," which today means Pod Security Admission or a policy engine. We cover both below.
Why it matters
Pods are isolated from the host by default, but that isolation is only as strong as the security context you allow. When nothing restricts pod specs, a few specific settings become dangerous.
Privileged containers
A pod that sets securityContext.privileged: true runs with nearly all the capabilities of the host. From there, an attacker can access host devices, load kernel modules, and in many cases break out of the container entirely.
Host namespace sharing
Settings like hostNetwork, hostPID, and hostIPC drop the boundary between the pod and the node. A pod with hostPID can see and signal every process on the node. A pod with hostNetwork can sniff traffic and bind to node ports.
Host path mounts
A hostPath volume lets a pod mount any directory from the underlying node. Mount / and the pod can read service account tokens, kubelet credentials, and secrets belonging to other workloads on that node.
Running as root
Containers that run as UID 0 widen the blast radius of any container escape. Combined with the items above, a root container is the typical starting point for privilege escalation.
The realistic attack chain looks like this: an attacker exploits a vulnerable app, lands in a pod that runs as root with a host mount, reads the kubelet's credentials off the node, and uses them to pull secrets from other pods. None of that requires a kernel zero-day if the pod was allowed to ask for too much in the first place.
Beyond direct attacks, unconstrained pods undermine compliance. CIS GKE Benchmark, PCI DSS, and most internal hardening standards expect that privileged and host-level access is denied by default.
How to fix it
The right fix depends on your cluster version. Pick the path that matches your environment.
Option 1: Pod Security Admission (recommended for modern clusters)
Pod Security Admission (PSA) is the built-in replacement for PSP. It enforces the Kubernetes Pod Security Standards at the namespace level using three levels: privileged (no restrictions), baseline (blocks known privilege escalations), and restricted (heavily hardened, follows best practices).
You apply it by labeling namespaces. Each namespace can enforce, audit, and warn independently, which lets you roll out gradually.
Start in audit and warn mode so you can see what would break before you enforce:
kubectl label namespace my-app \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/warn-version=latest \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/audit-version=latest
Once you have confirmed your workloads comply, switch on enforcement:
kubectl label namespace my-app \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=latest \
--overwrite
You can also apply these labels declaratively, which is the better long-term approach:
apiVersion: v1
kind: Namespace
metadata:
name: my-app
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/audit: restricted
Warning: Enforcing restricted on an existing namespace will block new pods that do not comply, including rollouts of existing Deployments. Always run in warn and audit mode first, fix the offending workloads, then flip enforce. Skipping this step can stall deployments during your next release.
Option 2: Gatekeeper or a policy engine
PSA only understands the three predefined standards. If you need custom rules, for example "deny images not from our registry" or "require a specific label," use a policy engine. GKE offers Policy Controller (a managed Gatekeeper) as part of GKE Enterprise, or you can install open-source Gatekeeper or Kyverno yourself.
A Gatekeeper constraint that blocks privileged containers looks like this:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPPrivilegedContainer
metadata:
name: deny-privileged-containers
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
excludedNamespaces: ["kube-system"]
To enable Policy Controller on a GKE cluster registered with a fleet:
gcloud container fleet policycontroller enable \
--memberships=my-cluster \
--project=my-project
Option 3: Legacy clusters still on PSP
If you are on a Kubernetes version that still supports PSP and cannot upgrade yet, enable the admission controller and apply a restrictive policy. Note this is a stopgap, not a destination.
gcloud container clusters update my-cluster \
--enable-pod-security-policy \
--zone=us-central1-a
Danger: Enabling PSP on a running cluster with no policies defined will deny all pod creation, because PSP defaults to deny when no policy authorizes a pod. This can take down your cluster's ability to schedule workloads, including system components. Define and bind permissive-then-restrictive policies for kube-system and your apps before you turn the controller on.
Plan your migration off PSP onto PSA or Gatekeeper. The Kubernetes project ships a migration guide, and tooling exists to translate existing PSP objects into equivalent PSA standards.
How to prevent it from happening again
Manual labeling drifts. The goal is to make "no pod security control" impossible to ship in the first place.
Bake it into your cluster provisioning
If you build clusters with Terraform, set the security posture and ensure namespaces are created with PSA labels through your platform tooling rather than by hand. A Terraform-managed namespace keeps the labels in source control:
resource "kubernetes_namespace" "app" {
metadata {
name = "my-app"
labels = {
"pod-security.kubernetes.io/enforce" = "restricted"
"pod-security.kubernetes.io/warn" = "restricted"
"pod-security.kubernetes.io/audit" = "restricted"
}
}
}
Gate pull requests with policy-as-code
Catch bad pod specs before they reach the cluster. Tools like conftest (OPA), Kyverno CLI, or kube-score can run in CI against your manifests:
# Fail the build if any manifest requests privileged mode
conftest test k8s/*.yaml --policy policy/security.rego
Tip: Run the same policy bundle in CI and at admission time in the cluster. When your pipeline and your Gatekeeper constraints share the exact same rules, developers get fast feedback in the PR and you still have a hard backstop at the API server. No surprises at deploy time.
Monitor continuously
Configuration drifts as namespaces are created, clusters are rebuilt, and teams add workloads. Lensix re-runs gke_podsecuritypolicy on a schedule so a cluster that loses its protection, or a new cluster that ships without it, surfaces quickly instead of sitting unnoticed.
Best practices
- Default to
restricted, exempt deliberately. Start every namespace at the restricted standard and grant exceptions only where a workload genuinely needs them, with documentation explaining why. - Never relax
kube-systemblindly. System components sometimes need elevated access. Scope exemptions to the specific namespace rather than weakening cluster-wide policy. - Combine PSA with other controls. Pod security is one layer. Pair it with workload identity, network policies, and node auto-upgrade for defense in depth.
- Drop all capabilities by default. In your pod specs, set
securityContext.capabilities.drop: ["ALL"]and add back only what a container requires. - Avoid
hostPathvolumes. Use persistent volumes, ConfigMaps, or Secrets instead. A host path is almost always a sign a workload is doing something it should not. - Run as non-root. Set
runAsNonRoot: trueand a specific high UID. Build images that do not need root, and your restricted policies will pass cleanly.
Pod security controls are cheap to enable and expensive to skip. A single labeled namespace turns a class of container-escape attacks from "trivial" into "needs a real exploit." Start in audit mode, fix what breaks, enforce, and then make it the default for every cluster you run.

