Safeguard
Container Security

Container configuration drift detection at runtime

Container images pass CI clean, but running containers drift within hours via exec sessions, sidecars, and webhooks. Here's how to detect it at runtime.

Vikram Iyer
Cloud Security Engineer
7 min read

A container built from a hardened base image with a read-only root filesystem, a non-root UID, and exactly three exposed ports can look completely different four hours after deployment. An engineer execs in to debug a memory leak, installs curl and strace, flips the filesystem back to read-write, and forgets to revert any of it. The image in the registry hasn't changed. The Kubernetes manifest in Git hasn't changed. But the running container is now a different attack surface than the one that passed CI. This is configuration drift, and it happens continuously — kubectl exec sessions, mutating webhooks, sidecar injectors, admission controller retries, and manual hotfixes all rewrite live container state without touching source control. Static scanning at build time catches none of it, because by definition it only ever sees the artifact before drift occurs. Runtime drift detection closes that gap by continuously diffing live container and pod state against the declared baseline, and flagging the delta the moment it appears.

What Is Container Configuration Drift?

Container configuration drift is any divergence between the security-relevant configuration you declared for a container — in a Dockerfile, Helm chart, or Kubernetes manifest — and the configuration that container is actually running with at a given moment. Common examples include a securityContext.readOnlyRootFilesystem: true setting that gets silently overridden to false by a debugging session, a pod that was deployed with capabilities: drop: [ALL] but is now running with NET_RAW or SYS_PTRACE added back by an operator, or a service that shipped listening on port 8080 but is now also bound to 9090 because a monitoring agent was hot-patched in. Drift also shows up in image identity: a pod spec that references myapp:1.4.2 in Git but is actually running myapp:1.4.2-debug after a kubectl set image rollback that never got reconciled. None of these changes require a new commit, a new pull request, or a new build — which is exactly why they're invisible to source-control-based reviews.

Why Doesn't Image Scanning Catch Runtime Drift?

Image scanning doesn't catch runtime drift because it inspects a static artifact at a single point in time — typically at build or push — while drift is, by definition, something that only exists in the running system afterward. A scanner that graded your image as clean on July 1 has no visibility into the fact that on July 3 a support engineer added hostNetwork: true to keep debugging tools working, or that an admission webhook injected an extra sidecar with a mounted /var/run/docker.sock. Gartner's container security guidance has flagged this exact gap for years: build-time and runtime are separate control planes, and organizations that only instrument one assume coverage they don't have. CIS Docker Benchmark and CIS Kubernetes Benchmark checks are frequently run once, at image-build time or cluster-provisioning time, and rarely re-evaluated against the live cluster on a recurring basis — which means a cluster that passed a CIS audit in Q1 can silently fail half those same checks by Q3 with nobody noticing.

What Kubernetes Events Commonly Trigger Drift?

The most common triggers are mutating admission webhooks, service mesh sidecar injection, manual kubectl patch/kubectl edit operations, and autoscaler-driven spec rewrites. Istio's and Linkerd's automatic sidecar injectors, for example, add an Envoy or proxy container to every annotated pod at admission time — typically increasing the container count per pod by one and adding new listening ports and iptables rules that never appear in the original deployment YAML. Mutating webhooks used for policy defaults (setting resource limits, injecting environment variables, adding labels) run on every pod creation and can override a subset of the fields a developer explicitly set. Horizontal and Vertical Pod Autoscalers rewrite resources.requests/limits continuously — dozens of times a day in high-churn clusters — which is benign for CPU/memory but becomes a drift signal when it interacts with QoS-class-dependent security policies. And manual kubectl exec sessions, used for live debugging, are the single largest source of undocumented filesystem and package-level drift because they leave no audit trail in Git at all — only in the Kubernetes audit log, if audit logging is even enabled.

How Do You Actually Detect Drift at Runtime?

You detect drift at runtime by continuously diffing the live state of running containers and pods against a declared, versioned baseline, rather than checking configuration once at deploy time. In practice this means three data sources working together: the Kubernetes API server's live object state (compared against the last-applied-configuration annotation or the Git-sourced manifest), the container runtime's own view of the running process (via the containerd or CRI-O API, which reveals actual mount points, capabilities, and namespaces regardless of what the pod spec claims), and kernel-level observation via eBPF, which can catch things neither the API server nor the runtime metadata will show — like a process opening a new listening socket or writing to a path that should be read-only. Tools such as Falco pioneered the eBPF-based approach for real-time syscall anomaly detection, while GitOps controllers like Argo CD's diff engine catch the subset of drift that happens at the Kubernetes-object level (their default reconciliation loop runs roughly every 3 minutes, which is fast for GitOps but far too slow to catch an active exploit). Effective drift detection combines both: object-level diffing for anything expressible in a manifest, and kernel-level monitoring for anything that only shows up in actual process behavior.

What Real-World Incidents Trace Back to Undetected Drift?

The clearest public case is Tesla's 2018 Kubernetes dashboard exposure, where researchers at RedLock (later Palo Alto Networks) found an internal Kubernetes console running without any authentication — a configuration that was never supposed to be internet-reachable but had drifted into that state and gone undetected long enough for attackers to deploy a cryptomining pod inside it. A second recurring pattern is exposed Docker daemon APIs: multiple honeypot studies from container security researchers have found that an internet-reachable, unauthenticated Docker socket (port 2375) is typically discovered and exploited by automated scanning botnets — such as the TeamTNT and Kinsing campaigns — within hours of first becoming reachable, not days or weeks. In both cases, the dangerous state wasn't present in the original infrastructure-as-code; it emerged afterward, through a load balancer rule change, a firewall exception left open past a maintenance window, or a debug flag that never got flipped back. Neither would have been caught by a pre-deployment scan, because the scan ran before the drift existed.

How Fast Does a Drift Detection System Need to Alert to Matter?

A drift detection system needs to alert within minutes, not hours, because the honeypot research above shows exploitation of newly-exposed misconfigurations starting almost immediately after exposure. A detection pipeline that batches results into a daily or weekly report — common in tools built around periodic compliance scanning — can log an alert about a container that ran privileged and unmonitored for 18 hours by the time anyone reads it. Mean-time-to-detect (MTTD) is the metric that matters here, and for drift specifically, the useful target is sub-5-minute detection from the moment the live state diverges from baseline, achieved by watching the Kubernetes audit log and container runtime events as a stream rather than polling cluster state on a fixed interval. Anything slower effectively concedes the exploitation window to automated attackers, who — per the campaigns above — are already scanning continuously for exactly this kind of opening.

How Safeguard Helps

Safeguard continuously diffs live Kubernetes and container runtime state against the SBOMs and manifests it generates or ingests at build time, so drift — an added capability, a reopened port, a swapped base image — is flagged against a real baseline rather than a generic best-practice checklist. Griffin AI correlates each drift event with the workload's actual exposure and behavior to cut alert noise, distinguishing a sidecar injection from your own service mesh from an unauthorized capability addition on an internet-facing pod. Reachability analysis layers on top of that, telling you whether a drifted container is actually exposed to exploitable, network-reachable code paths before your team spends time triaging it. And where the drift traces back to a fixable root cause — a manifest missing a securityContext field, a stale base image tag — Safeguard opens an auto-fix pull request against the source manifest so the declared state and the runtime state converge again without a manual patch cycle.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.