Workloads with containers not pinned to a non-root user
What does ZopNight detect here?
ZopNight flags an EKS, GKE or AKS Deployment, StatefulSet or DaemonSet when any container or init container lacks both `runAsNonRoot: true` and a `runAsUser` above 0. Without either, the process runs as whatever user the image declares, which may be UID 0, and the Pod Security Standards Restricted profile does not allow that pod.
Signal and threshold
| Field | Value |
|---|---|
| Rule IDs | RC-1723 · RC-1823 · RC-1923 · RC-1724 · RC-1824 · RC-1924 · RC-1725 · RC-1825 · RC-1925 |
| Category | security |
| Severity | high |
| Metric | none — pure configuration read |
| Threshold | neither runAsNonRoot true nor runAsUser above 0 |
| Source | ZopNight |
| Permissions used | list deployments.apps · list statefulsets.apps · list daemonsets.apps |
Where it applies
Leaving the user to the image
Kubernetes does not pick a user for a container. The
Pod API reference
says runAsUser defaults to the user specified in the image metadata, so an image built without
a USER instruction can run its process as UID 0. runAsNonRoot: true closes the gap: the
kubelet then checks the image at runtime and refuses to start the container if it would run as
root. Neither setting is on by default.
The Pod Security Standards
put both controls in the Restricted profile: runAsNonRoot must be true, and runAsUser must not
be 0. A root process inside a container is not root on the host by itself, but it removes a layer
of defence, so a container escape or a mounted host path becomes far more damaging.
Auditing containers for a non-root guarantee
This lists containers and init containers that have neither guarantee at container or pod level:
kubectl get deployments,statefulsets,daemonsets -A -o json | jq -r ' .items[] | .kind as $k | .metadata as $m | .spec.template.spec.securityContext as $p | (.spec.template.spec.containers + (.spec.template.spec.initContainers // []))[] | select(((if .securityContext.runAsNonRoot == null then ($p.runAsNonRoot // false) else .securityContext.runAsNonRoot end) | not) and ((.securityContext.runAsUser // $p.runAsUser // 0) == 0)) | "\($k) \($m.namespace)/\($m.name) \(.name)"'Proof of non-root is required, not assumed
ZopNight evaluates the security settings recorded for each container and init container in
Deployments, StatefulSets and DaemonSets. A container passes only when it explicitly sets
runAsNonRoot: true, or sets runAsUser to a number above 0. Everything else counts: no
security context, runAsNonRoot: false, or runAsUser: 0. One finding per workload lists every
offending container by name, so an init container and an app container can appear together.
Where it has nothing to say
A workload with no containers recorded produces no finding, and one in which every container asserts a non-root user is quiet. Ephemeral debug containers are not part of the check. Privileged mode and host networking are separate risks with their own findings: Privileged container and Container with host network.
Security exposure, no saving
This high-severity security finding has no cost figure. The risk is the blast radius of a compromised container, which is larger when its processes hold UID 0.
Moving containers off root
- Check whether the image needs root at all. Many application images can run as an unprivileged user once file ownership is fixed.
- Add a non-root
USERto the Dockerfile and rebuild the image. - Set
securityContext.runAsNonRoot: trueand a non-zerorunAsUser, such as 1000, on the pod or on each container. Container settings override pod settings where both are present. - Roll out and confirm the pods start; a container that still needs root will fail its start check rather than run silently as root.
- Label the namespace
pod-security.kubernetes.io/warn: restrictedto surface violations, then move toenforceonce workloads comply.