Kubernetes Insecure Pod/Workload Spec
Deep-walk detection of insecure Kubernetes pod and workload specs embedded in Ansible playbooks via kubernetes.core.k8s (and similar) modules.
37 rules in k8s_insecure_spec.yml
CRITICAL: 5 | HIGH: 18 | MEDIUM: 11 | LOW: 3
| Rule ID | Severity | Title | Description | Refs |
|---|---|---|---|---|
ingress_ | CRITICAL | ingress-nginx configuration-snippet Annotation Enabled (IngressNightmare CVE-2025-1974) | A task renders an Ingress manifest with nginx.ingress.kubernetes.io/configuration-snippet, server-snippet, auth-snippet, or modsecurity-snippet annotations AND the cluster’s ingress-nginx controller does not have allow-snippet-annotations: false in its ConfigMap. configuration-snippet allows arbitrary Lua/Nginx directives to be injected via a namespace-level Ingress annotation - which, pre-CVE-2025-1974 (IngressNightmare, Wiz Research Mar 2025), means any user with Ingress-write permission in ANY namespace can template directives that get loaded by the cluster-wide ingress controller running as a privileged pod with cluster-admin-like secrets access. Unauthenticated pre-auth RCE when combined with the admission-controller path. | |
k8s_ | CRITICAL | Pod Shares Host PID/Network/IPC Namespace | A Pod, Deployment, StatefulSet, or DaemonSet spec combines two or more of hostPID: true, hostNetwork: true, hostIPC: true. Any of the three alone is dangerous; combining them effectively gives the pod full host visibility - it can enumerate every process on the node (hostPID), bind to every host port (hostNetwork), and read/write host shared-memory segments (hostIPC). This is the same combination used by ‘host-escape’ operator images and by Kinsing/TeamTNT miner deployments. | |
k8s_ | CRITICAL | Kubernetes Container Runs as Privileged | Container’s securityContext sets privileged: true, granting equivalent privileges to processes on the host - effectively full root access to the node, including ability to access /dev, load kernel modules, and escape the container. | |
kubelet_ | CRITICAL | kubelet anonymous-auth=true Enabled (Unauthenticated Kubelet API) | A kubelet config sets authentication.anonymous.enabled: true or passes --anonymous-auth=true. This allows ANY unauthenticated request to the kubelet API on TCP/10250 to hit /run, /exec, /portForward, and /logs/* endpoints - i.e. full container-exec on every pod scheduled on that node. This is the Kubernetes equivalent of PermitRootLogin without-password with passwordless root, and is the most-exploited kubelet misconfiguration (CVE-like pattern exploited by TeamTNT, Kinsing, Siloscape) even though it is not a CVE. | |
rbac_ | CRITICAL | RoleBinding/ClusterRoleBinding Grants cluster-admin Or system:masters To Non-Default Subject | A task renders a ClusterRoleBinding (or RoleBinding with roleRef.kind: ClusterRole) whose roleRef.name is cluster-admin OR whose subjects[].name contains system:masters - to a ServiceAccount, Group, or User that is not one of the platform-baseline subjects (system:nodes, system:kube-controller-manager). cluster-admin is the superuser role; system:masters is the certificate-authenticated superuser group (bypasses RBAC entirely). Either granted to a namespace-scoped ServiceAccount makes that SA’s token a cluster-takeover primitive - the 2024 Goose/Ransomware-in-Kubernetes campaigns specifically hunt for these bindings. | |
crio_ | HIGH | CRI-O runtime-level privileged_without_host_devices Enabled | Sets privileged_without_host_devices = true in a /etc/crio/crio.conf or /etc/crio/crio.conf.d/*.conf runtime block. That makes every privileged Pod scheduled on the node automatically get host-device access (/dev/*), silently turning Pod Security Admission privileged from a capability-only escalation into a full host-devices breakout. Documented attacker primitive on managed OpenShift / Talos / Fedora-CoreOS clusters. | |
k8s_ | HIGH | Kubernetes Pod Annotated AppArmor Unconfined | container.apparmor.security.beta.kubernetes.io/<container>: unconfined (or the 1.30+ securityContext.appArmorProfile.type: Unconfined) disables the AppArmor LSM for that container. AppArmor blocks a meaningful subset of host-touching syscalls (mount, ptrace of non-child, raw socket) by default; disabling it removes a cheap layer of defense-in-depth on Debian/Ubuntu-derived nodes. | |
k8s_ | HIGH | Kubernetes Audit Policy Rule With level: None Or level: Metadata On Mutating Verbs | A task renders a Kubernetes audit-policy YAML (/etc/kubernetes/audit-policy.yaml, passed to kube-apiserver via --audit-policy-file) with a rule matching create/update/patch/delete verbs OR sensitive resources (secrets, configmaps, roles, clusterrolebindings, serviceaccounts/token) but with level: None (total silence) or level: Metadata (no request/response bodies - hides the actual changes made). Attackers post-compromise of the control plane frequently modify the audit policy to silence their own mutations while leaving enough events to pass compliance dashboards. CIS Kubernetes Benchmark 1.9 explicitly requires level: RequestResponse for secrets, token-requests, and RBAC mutations. Distinct from the generic kube_apiserver_no_audit_log rule; this catches the targeted-silence variant. | |
k8s_ | HIGH | Kubernetes Container Adds Dangerous Linux Capability | Container’s securityContext.capabilities.add grants a high-risk capability (SYS_ADMIN, NET_ADMIN, SYS_PTRACE, DAC_READ_SEARCH, SYS_MODULE, etc.) that enables container escape or host-level tampering. | |
k8s_ | HIGH | Kubernetes Pod Uses hostIPC: true | Pod shares the host’s IPC namespace - can read shared-memory regions and semaphores from other host processes, including kubelet-managed workloads. | |
k8s_ | HIGH | Kubernetes Pod Uses hostNetwork: true | Pod shares the host’s network namespace - can sniff node traffic, bind to privileged ports, bypass NetworkPolicies, and reach services on the host loopback. | |
k8s_ | HIGH | Kubernetes Pod Uses hostPID: true | Pod shares the host’s PID namespace - can see and signal every process on the node, including kubelet. Enables trivial container escape via /proc/1/root and cross-pod credential harvesting. | |
k8s_ | HIGH | Kubernetes Pod Mounts Host Path | hostPath volume gives the pod direct read/write access to the node filesystem - can mount /var/run/docker.sock, /etc, /proc, /root, or the kubelet client cert and escape to the host. | |
k8s_ | HIGH | MutatingWebhookConfiguration failurePolicy=Ignore On Security-Critical Resources | A task renders a MutatingWebhookConfiguration or ValidatingWebhookConfiguration with failurePolicy: Ignore (if the webhook is unreachable, the API request PROCEEDS without validation/mutation) AND rules matching security-sensitive resources: pods, deployments, secrets, serviceaccounts, roles, rolebindings. Admission webhooks are the primary enforcement point for PodSecurity baselines, image-provenance (Sigstore, Kyverno), and runtime-class pinning (gVisor, Kata). failurePolicy: Ignore means a simple DoS of the webhook (network policy blocking port 443 to the webhook service, pod eviction during rolling update, or cert-expiry) silently disables enforcement. Attackers post-cluster-compromise routinely patch the webhook service to fail-fast, then schedule privileged pods that would normally be blocked. CIS K8s Benchmark 1.9 explicitly requires failurePolicy: Fail on security webhooks. | |
k8s_ | HIGH | Kubernetes securityContext.allowPrivilegeEscalation=true without no-new-privileges | A Pod spec either explicitly sets securityContext.allowPrivilegeEscalation: true and omits a matching no_new_privs Pod Security Standard, or sets the CRI-level annotation io.kubernetes.cri-o.TrySkipVolumeSELinuxLabel alongside a suid-containing image. Without no_new_privileges, a suid binary inside the container can still gain privileges at exec() time even if the caller dropped capabilities - this bypass has been used in three public CVE-chains since 2024. | |
k8s_ | HIGH | Kubernetes Container Uses seccompProfile Unconfined | securityContext.seccompProfile.type: Unconfined disables the seccomp syscall filter, giving the container access to the full ~400-syscall kernel surface. The Linux kernel is the single largest attack surface exposed to a container; without seccomp, kernel-exploit CVEs (Dirty Pipe, cgroups v1 release_agent, nf_tables, io_uring) become directly weaponizable from inside the pod. | |
k8s_ | HIGH | Kubernetes TokenRequest With expirationSeconds > 86400 (Long-Lived ServiceAccount JWT) | A task issues a Kubernetes TokenRequest or BoundServiceAccountTokenVolume with spec.expirationSeconds greater than 86400 (24 hours) - or uses the legacy kubernetes.io/service-account-token secret which issues non-expiring tokens. Modern K8s (≥1.22) uses bound, short-lived projected service-account tokens (default TTL 1 hour, auto-rotated) - requesting a >24h token is explicitly a step away from that posture. 7-day, 30-day, or literal ‘infinity’ (expirationSeconds: 2147483647) tokens are a favorite persistence technique in 2024 cluster-compromise campaigns (Microsoft Defender: SiegedSec, TeamTNT, Peach Sandstorm) - an attacker with a one-time pods/exec primitive issues a multi-year token, exfiltrates it, and has persistent cluster access even after the original entry is closed. The audience field also matters: audiences: [""] / omitted = the default kube-apiserver audience = full in-cluster authN. | |
k8s_ | HIGH | Kubernetes RBAC Rule Uses Wildcard verbs/resources/apiGroups | A Role or ClusterRole rule sets verbs, resources, or apiGroups to '*'. A wildcard verb on any resource allows create/update/delete/exec/portforward; a wildcard resource on any verb includes secrets and tokenreviews; a wildcard apiGroup covers every CRD that ever gets installed. This is the #1 RBAC over-grant pattern and effectively equivalent to cluster-admin for the subjects bound to it. | |
kubelet_ | HIGH | kubelet read-only-port=10255 Enabled (Unauthenticated Node Metadata Leak) | A kubelet config (/var/lib/kubelet/config.yaml, --read-only-port, KubeletConfiguration.readOnlyPort) sets readOnlyPort: 10255 or passes --read-only-port=10255. The kubelet read-only API on TCP/10255 is UNAUTHENTICATED and UNENCRYPTED and exposes: /pods (every pod spec with env-vars - often containing secrets/tokens), /stats/* (cgroup metrics), /spec/ (node spec), /metrics/*. This is the direct data source used in the 2024 Wiz-reported cryptojacking campaigns that scanned cloud ASNs for 10255/tcp and pulled AWS creds from pod env. Kubernetes 1.10+ defaults to 0 (disabled). | |
kubernetes_ | HIGH | Kubernetes namespace or cluster has Pod Security Admission disabled or set to privileged | A task renders a namespace manifest with pod-security.kubernetes.io/enforce: privileged (the escape-hatch label that disables all PSA enforcement for that namespace), OR a kube-apiserver manifest with --admission-control-config-file referencing a config that sets PSA default.enforce: privileged, OR omits the pod-security.kubernetes.io/enforce label entirely on a production namespace while the cluster-wide default is privileged. Since PSP was removed in Kubernetes 1.25, PSA is the ONLY in-tree guardrail against privileged / hostPath / hostNetwork pods - a namespace at privileged PSA gives any pod-creator root on the node. | |
opa_ | HIGH | OPA Gatekeeper Constraint Or Kyverno ClusterPolicy In dryrun/Audit On Critical Policy | A task renders a Gatekeeper Constraint with enforcementAction: dryrun or enforcementAction: warn, OR a Kyverno ClusterPolicy/Policy with validationFailureAction: Audit - on a policy whose name / labels indicate it is a baseline-security control (disallow-privileged, disallow-host-namespaces, require-non-root, restrict-image-registries, verify-image-signatures, psp-*, pss-*, security.tier=critical). Non-enforcing admission policies on baseline-security rules are ‘policy theatre’ - they log violations into observability but do nothing to prevent the violating pod from scheduling and running. | |
pod_ | HIGH | Pod Spec hostAliases Injects Cloud Metadata IP (SSRF Bait) | A Pod/Deployment/StatefulSet spec has hostAliases: mapping the cloud instance-metadata IP (169.254.169.254 on AWS/GCP, or 100.100.100.200 on Aliyun) to an attacker-controlled hostname. Any container inside the pod that resolves the spoofed hostname (e.g. an app config pointing at metadata.internal) will be redirected back to the genuine metadata service - but this is the direct primitive the attacker uses to enable SSRF from the pod. The 2024 Wiz ‘Hello Kubelet’ research describes this as a pod-level IMDS redirect used in multi-tenant cluster takeover chains. | |
pod_ | HIGH | Namespace Labelled With Pod Security Admission enforce=privileged (Or No enforce Label) | A task creates/updates a Namespace with label pod-security.kubernetes.io/enforce: privileged (or omits the enforce label entirely, which falls back to the cluster default that is privileged in most installs until PodSecurityAdmission is explicitly wired to baseline or restricted). privileged PSA allows hostPath, hostNetwork, hostPID, privileged containers, and arbitrary capabilities - the entire pre-PSP set of workload escapes. This is the single biggest regression in clusters that migrated from PodSecurityPolicy (removed in 1.25) without migrating to PSS/PSA properly. | |
ephemeral_ | MEDIUM | ephemeralContainers Debug Image Pinned To :latest Or No Digest | A Pod spec or a kubectl debug invocation creates ephemeralContainers with image: pointing at a debug-toolbox image tagged :latest or without an @sha256:... digest (nicolaka/netshoot:latest, busybox, alpine, ubuntu). Ephemeral containers share the target pod’s network + process namespace (via targetContainerName) - a typo-squatted or compromised :latest debug image is an immediate pivot into the production pod’s env, secrets-mount, and ServiceAccount token. Exploited in 2024 Wiz research by publishing a malicious nicolaka/netshoot copy on Docker Hub. | |
k8s_ | MEDIUM | Kubernetes Container Allows Privilege Escalation | securityContext.allowPrivilegeEscalation: true (or missing) permits a container process to gain more privileges than its parent - enabling setuid binaries to escalate to root inside the container. | |
k8s_ | MEDIUM | Kubernetes Pod Auto-Mounts ServiceAccount Token | automountServiceAccountToken defaults to true - any RCE in the pod gets a kubeconfig for the pod’s SA, enabling lateral movement to the API server. Must be explicitly disabled unless the workload calls the API. | |
k8s_ | MEDIUM | Kubernetes Pod Uses Default ServiceAccount | Pod omits serviceAccountName (or sets it to ‘default’). The default service account in many clusters is granted broad read/list permissions via legacy RBAC, and its token is auto-mounted into every container. | |
k8s_ | MEDIUM | Kubernetes Pod Ships Debug Ephemeral Container | Pod spec has an ephemeralContainers: entry referencing a debug image (busybox, alpine, nicolaka/netshoot, nixery.dev/shell). Ephemeral containers share the target pod’s namespaces and SA token; a debug sidecar shipped in production manifests gives every pod a pre-authenticated recon shell with full kubectl access via the mounted SA token, regardless of the app’s own hardening. | |
k8s_ | MEDIUM | Kubernetes Container Binds hostPort in Privileged Range | container.ports[].hostPort is set to a value <1024 (privileged port range). A hostPort binding schedules at most one pod per node on that port, bypasses Service/LoadBalancer, and in the privileged range frequently clashes with node-level services (sshd/22, DNS/53, kubelet/10250). The port is bound on every node’s primary interface with no NetworkPolicy applicable - effectively a cluster-wide ingress hole. | |
k8s_ | MEDIUM | Kubernetes Container Image Uses :latest (Mutable Tag) | Container spec sets image: <repo>:latest or any other mutable tag. :latest is re-pointed on every upstream push - two pods with identical manifests can run different code, rollback by manifest is impossible, and a compromised registry can silently ship malicious content to the next pod restart. This is the K8s equivalent of pip install package with no version. | |
k8s_ | MEDIUM | PodDisruptionBudget With maxUnavailable=0 Or minAvailable=100% (Blocks All Evictions, Scheduler-DoS) | A task creates a PodDisruptionBudget (PDB) with maxUnavailable: 0 or minAvailable: 100% (equivalent) applied to a Deployment / StatefulSet. A PDB with these values means the eviction-subresource for the targeted pods NEVER succeeds - blocking node-drain (required for kernel patches, K8s version upgrades, autoscaler scale-down, and graceful disk replacement), blocking Cluster Autoscaler from consolidating workloads, and creating a DoS condition for the cluster itself. Distinct from the legitimate maxUnavailable: 1 on a 3-replica deployment - this rule catches the absolute-zero case. Used in 2024 LockBit 3.0 ransomware deployments to pin attacker-controlled pods to specific nodes + prevent automated remediation via kubectl drain. | |
k8s_ | MEDIUM | Kubernetes Container Runs as UID 0 (root) | securityContext.runAsUser: 0 or runAsNonRoot: false permits the container process to run as root, increasing the blast radius of any RCE vulnerability inside the container. | |
kubernetes_ | MEDIUM | Kubernetes ServiceAccount token automount not disabled (pod can call API server by default) | A task creates a ServiceAccount or Pod/Deployment spec WITHOUT automountServiceAccountToken: false. By default, Kubernetes projects a JWT for the namespace’s default SA into every pod at /var/run/secrets/kubernetes.io/serviceaccount/token - any process in the pod (including a compromised dependency) can call the Kubernetes API with the SA’s RBAC. Even the default SA in a hardened namespace typically has get pods + get secrets inside its namespace - enough to enumerate and exfiltrate. Workloads that don’t need API access MUST opt out. | |
validating_ | MEDIUM | ValidatingAdmissionPolicy With validationActions=[Audit] Only On Pod/Deployment (Security Policy In Warn-Only) | A ValidatingAdmissionPolicyBinding (K8s 1.30+ GA feature) for a policy that matches Pods/Deployments/DaemonSets/StatefulSets sets validationActions: [Audit] (or [Warn]) instead of including Deny. Audit-only means violating pods are admitted, a log line is emitted, and the cluster continues to run with the violation. This is the new K8s-native Pod Security equivalent of a Gatekeeper/Kyverno dryrun policy - seen in production clusters that migrated off Gatekeeper to VAP and forgot to flip from Audit to Deny. | |
k8s_ | LOW | Kubernetes Container Missing Resource Limits | Container spec omits resources.limits (and often resources.requests). Without limits, a single compromised or buggy pod can consume all CPU/memory on its node, triggering OOM-kills of co-tenants (noisy-neighbor DoS) and driving the node into NotReady. For memory specifically, missing limits means the container can never be OOM-killed early by cgroup - it keeps growing until it evicts legitimate workloads. | |
k8s_ | LOW | Kubernetes Container readOnlyRootFilesystem: false | readOnlyRootFilesystem left unset or false means the container can write anywhere in its rootfs, which aids malware persistence and disk-based tampering on compromise. | |
k8s_ | LOW | Kubernetes Service Uses Type: NodePort | A Service sets type: NodePort, which opens a port (default 30000-32767) on every node, bound to 0.0.0.0. NodePort bypasses Ingress/LoadBalancer controls (no TLS termination, no WAF, no rate-limiting) and the port is reachable from any node’s external NIC - turning every worker node into a direct ingress target. |