Safeguard
Container Security

A Practical Container Security Checklist: From Base Image to Runtime

Standard Docker Hub images ship 50-60 known CVEs on average. Here's the checklist that gets containers from base image to runtime without carrying them along.

Safeguard Research Team
Research
6 min read

A container built from an unexamined base image commonly inherits 50 to 60 known vulnerabilities before a single line of application code is added, with 15 to 20 of those typically rated high or critical depending on the image and scan date. That statistic alone is why the CIS Docker Benchmark opens its image-hardening section with a blunt recommendation: start minimal, and remove what you don't need. But base image hygiene is only the first of three checkpoints a container passes through before it's exposed to production traffic — build-time scanning and runtime monitoring are the other two, and each catches a different class of failure the others miss. The XZ Utils backdoor, tracked as CVE-2024-3094 and disclosed on March 29, 2024, showed that even a trusted, widely-vendored compression library can be deliberately sabotaged upstream of any scan. The runc container-escape flaw CVE-2024-21626, fixed on January 31, 2024, showed that runtime components — not just application dependencies — need their own patch-tracking discipline. This post lays out a checklist spanning all three stages, grounded in what's actually happened in the wild rather than theoretical risk.

What actually makes a base image a good starting point?

A good base image is small, current, and still scanned — in that order, but never skipping the last step. The CIS Docker Benchmark's core guidance is to prefer minimal images (Alpine, distroless, or scratch-based builds) over full Debian, Ubuntu, or RHEL bases, and to strip build tools, package managers, and shell utilities that an attacker could use post-compromise but that a running application never touches. Fewer installed packages means a smaller CVE surface by construction. But "smaller" doesn't mean "safe by default": the 2021 arXiv study "Well Begun is Half Done" found that minimal and distroless images can still carry proportionally severe CVEs relative to their size — a handful of packages in a 20MB image can matter as much as fifty in a 900MB one if one of them is a network-facing library. The practical takeaway is that image size reduction and vulnerability scanning are two separate controls, not a substitute for one another — a distroless image still needs to be scanned on every build, not assumed clean because it's small.

Why isn't a clean build-time scan enough on its own?

A build-time scan only proves that the image was clean against the CVE database that existed at the moment it ran — it says nothing about who built the components inside it or whether they were tampered with before the scan ever saw them. That distinction became impossible to ignore after CVE-2024-3094: a maintainer account for XZ Utils, a compression library (liblzma) pulled into countless base images and build chains, inserted a multi-stage backdoor into release tarballs over roughly two years before a Microsoft engineer noticed anomalous SSH login latency and traced it back. A CVE scan run against the compromised version would have found nothing, because no CVE existed yet — the backdoor was only caught by an alert engineer, not a scanner. Log4Shell (CVE-2021-44228, December 2021) is the reference case for a related build-time gap: the vulnerable component was routinely three or four dependency layers deep, invisible to a manifest scan that only checks declared top-level dependencies. Both cases point to the same fix: generate a full SBOM (CycloneDX or SPDX) at build time that captures every transitive dependency, and track build provenance — not just CVE matches — so a compromised upstream source is at least attributable after the fact.

What does the runc container-escape CVE teach about patching runtime components?

It teaches that the container runtime itself — runc, containerd, the shim layer — needs the same patch discipline as application dependencies, because a flaw there can break the isolation boundary the whole security model depends on. CVE-2024-21626, disclosed and fixed on January 31, 2024, stemmed from a leaked internal file descriptor in runc: a malicious or compromised image could set its working directory to something like /proc/self/fd/N, tricking the container runtime into operating on a host filesystem path during container startup and escaping the container boundary entirely. The fix shipped in runc 1.1.12, Docker 25.0.2, and containerd 1.6.28/1.7.13 — all released the same week. The lesson for a checklist is concrete: vulnerability management for containers has to include the host's container engine and OCI runtime versions, not just the packages baked into application images, because a scan of your Dockerfile layers will never surface a flaw that lives in the daemon running underneath them.

What should runtime monitoring actually watch for?

Runtime monitoring should watch for the things a build-time scan structurally cannot see: what a container actually does once it's executing, as opposed to what it was built to be capable of doing. NIST SP 800-190, the Application Container Security Guide, and the CIS Benchmark both call out behavioral monitoring — unexpected process execution inside a container, privileged syscalls, unplanned network egress, and unauthorized file access — as a distinct control layer from image scanning, because a scan captures a point-in-time snapshot while a running container can drift from it through a compromised process, a misconfigured mount, or an exploited application flaw that spawns a shell. The current industry-standard mechanism for collecting this telemetry cheaply is eBPF: it observes process execution, library loads, and network connections directly from the kernel without requiring an in-process agent inside every container, which keeps overhead low enough to run on every production node continuously rather than sampled.

How Safeguard Helps

Safeguard runs the checklist as one connected pipeline instead of three disconnected tools. Continuous scanning rescans every container image on push and re-evaluates running images roughly every four hours, so an image that was clean when it shipped gets flagged automatically the moment a new CVE lands against something inside it — closing the build-time-only gap that let cases like the runc flaw sit unnoticed in already-deployed images. The runtime protection layer ships ATT&CK-mapped rule packs today — including a dedicated container-escape detection pack tuned to the namespace-breakout and privileged-mount techniques behind flaws like CVE-2024-21626 — with an eBPF-based collector rolling out for Linux to feed it live events, so behavioral drift is caught even when it doesn't match a known CVE. And when a new base-image CVE is published, self-healing containers can automatically rebuild and redeploy the patched image; customer tenants see a median time-to-heal of 20 to 45 minutes from CVE publication to production rollout, turning what used to be a multi-day patch cycle into something closer to real time.

Never miss an update

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