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 IDSeverityTitleDescriptionRefs
ingress_nginx_snippet_annotation_enabledCRITICALingress-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_pod_shares_host_namespaceCRITICALPod Shares Host PID/Network/IPC NamespaceA 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_privileged_containerCRITICALKubernetes Container Runs as PrivilegedContainer’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_anonymous_auth_enabledCRITICALkubelet 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_binds_cluster_admin_or_system_mastersCRITICALRoleBinding/ClusterRoleBinding Grants cluster-admin Or system:masters To Non-Default SubjectA 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_privileged_runtime_defaultHIGHCRI-O runtime-level privileged_without_host_devices EnabledSets 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_apparmor_unconfinedHIGHKubernetes Pod Annotated AppArmor Unconfinedcontainer.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_audit_policy_level_none_or_metadata_onlyHIGHKubernetes Audit Policy Rule With level: None Or level: Metadata On Mutating VerbsA 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_capabilities_add_dangerousHIGHKubernetes Container Adds Dangerous Linux CapabilityContainer’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_host_ipcHIGHKubernetes Pod Uses hostIPC: truePod shares the host’s IPC namespace - can read shared-memory regions and semaphores from other host processes, including kubelet-managed workloads.
k8s_host_networkHIGHKubernetes Pod Uses hostNetwork: truePod shares the host’s network namespace - can sniff node traffic, bind to privileged ports, bypass NetworkPolicies, and reach services on the host loopback.
k8s_host_pidHIGHKubernetes Pod Uses hostPID: truePod 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_hostpath_volumeHIGHKubernetes Pod Mounts Host PathhostPath 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_mutating_webhook_failure_policy_ignore_on_security_resourcesHIGHMutatingWebhookConfiguration failurePolicy=Ignore On Security-Critical ResourcesA 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_no_new_privileges_falseHIGHKubernetes securityContext.allowPrivilegeEscalation=true without no-new-privilegesA 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_seccomp_unconfinedHIGHKubernetes Container Uses seccompProfile UnconfinedsecurityContext.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_tokenrequest_expirationseconds_long_livedHIGHKubernetes 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_wildcard_rbacHIGHKubernetes RBAC Rule Uses Wildcard verbs/resources/apiGroupsA 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_read_only_port_10255_enabledHIGHkubelet 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_pod_security_admission_disabledHIGHKubernetes namespace or cluster has Pod Security Admission disabled or set to privilegedA 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_gatekeeper_kyverno_dryrun_on_critical_policyHIGHOPA Gatekeeper Constraint Or Kyverno ClusterPolicy In dryrun/Audit On Critical PolicyA 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_hostaliases_metadata_ip_ssrf_baitHIGHPod 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_security_admission_privileged_namespaceHIGHNamespace 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_container_debug_image_latest_tagMEDIUMephemeralContainers Debug Image Pinned To :latest Or No DigestA 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_allow_privilege_escalationMEDIUMKubernetes Container Allows Privilege EscalationsecurityContext.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_automount_sa_tokenMEDIUMKubernetes Pod Auto-Mounts ServiceAccount TokenautomountServiceAccountToken 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_default_service_accountMEDIUMKubernetes Pod Uses Default ServiceAccountPod 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_ephemeral_debug_containerMEDIUMKubernetes Pod Ships Debug Ephemeral ContainerPod 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_hostport_privilegedMEDIUMKubernetes Container Binds hostPort in Privileged Rangecontainer.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_image_latest_or_untaggedMEDIUMKubernetes 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_poddisruptionbudget_blocks_all_evictionsMEDIUMPodDisruptionBudget 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_run_as_rootMEDIUMKubernetes 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_sa_token_automount_not_disabledMEDIUMKubernetes 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_admission_policy_audit_only_on_podsMEDIUMValidatingAdmissionPolicy 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_no_resource_limitsLOWKubernetes Container Missing Resource LimitsContainer 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_readonly_root_filesystem_falseLOWKubernetes Container readOnlyRootFilesystem: falsereadOnlyRootFilesystem left unset or false means the container can write anywhere in its rootfs, which aids malware persistence and disk-based tampering on compromise.
k8s_service_nodeportLOWKubernetes Service Uses Type: NodePortA 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.