Safeguard
Container Security

Reducing CVEs in container base images

Base images inherit hundreds of OS-level CVEs your app never touches. Here's how reachability analysis and minimal bases cut real risk, not just counts.

Michael
Cloud Security Architect
7 min read

Container base images ship far more than your application needs. Pull python:3.12-slim and you get roughly 90 OS-level packages beyond the Python runtime — glibc, perl, coreutils, apt itself — each carrying its own CVE history whether your code ever touches it or not. A 2023 Sysdig Cloud-Native Security report found that 87% of container images running in production contained at least one critical or high-severity vulnerability, and the vast majority came from the base layer, not from application dependencies. That gap between "vulnerability present" and "vulnerability exploitable" is where most container security programs waste their remediation budget: chasing CVEs in a perl binary that's never invoked instead of the three packages an attacker could actually reach.

This post breaks down where base image CVEs actually come from, which mitigations move the needle versus which ones just move the number, and how to build a remediation workflow that doesn't require re-triaging the same 200 findings every sprint.

What makes a container base image accumulate CVEs?

Base images accumulate CVEs because they inherit the full package manifest of whatever OS distribution they're built on, not just the runtime your application needs. A standard ubuntu:22.04 image installs around 90 packages by default — dpkg, apt, gpg, login, systemd-adjacent libraries — none of which a typical microservice calls at runtime. Language runtime images compound this: node:20-bookworm layers the Node.js binary on top of Debian bookworm's full package set, so a scan of that image regularly surfaces 300–400+ known CVEs, the overwhelming majority in OS packages like openssl, krb5, and libxml2 rather than in Node itself or your node_modules. Every one of those packages is a maintenance liability: when a fix lands upstream, you inherit it only on your next rebuild, and until then the CVE sits in your image whether or not any code path reaches it.

Does switching to Alpine or a distroless base actually reduce CVE counts?

Yes — switching base distributions is the single highest-leverage change you can make, but the size of the reduction depends on which alternative you pick. Alpine Linux, built on musl libc and BusyBox instead of glibc and GNU coreutils, ships as a ~5MB image versus Debian slim's ~80MB, and that smaller package set typically cuts published CVE counts by 70–90%: where node:20-bookworm shows 300+ CVEs, node:20-alpine commonly shows fewer than 20. Google's distroless images (gcr.io/distroless/*) go further by removing the package manager, shell, and most userland utilities entirely — there's no /bin/sh, no apt, no apk, so there's nothing for a scanner to flag beyond the language runtime and your compiled application. The tradeoff is real: Alpine's musl libc breaks native Node addons and some Python C-extensions compiled against glibc, and distroless images have no shell for debugging, which means you need a sidecar or ephemeral debug container to exec into a running pod. Teams that made this switch in 2022–2023 (Datadog and Chainguard both documented internal migrations) reported CVE count drops in that same 70–90% range, paired with real engineering time spent fixing musl compatibility issues on the way in.

How many of the CVEs flagged in a base image are actually exploitable?

Usually a small fraction — reachability analysis typically shows that only 5–15% of the CVEs a scanner flags sit in a code path your application actually loads, imports, or executes at runtime. A CVE in libxml2's XML external entity handling matters if your app parses untrusted XML; it doesn't matter if the library is present only because it's a transitive dependency of a package manager that never runs after build time. Traditional SCA scanners report at the package level: "package X version Y is present, and version Y has CVE Z" — full stop, with no distinction between a package that's dlopen()'d on every request and one that's dead weight from a base image's default install list. That's why two teams scanning the identical python:3.11-bookworm image can walk away with wildly different remediation priorities: one patches all 150 flagged CVEs in order of CVSS score, the other identifies the 12 that are actually loaded into the running process and fixes those first. Reachability-aware triage is also why "reduce CVE count" and "reduce risk" aren't the same KPI — a base image swap that drops your CVE count from 300 to 40 is good, but confirming which of those 40 are reachable is what actually tells you whether you shipped safer software.

What's the fastest way to remediate a critical base image CVE like Log4Shell or the curl SOCKS5 bug?

The fastest path is rebuilding and redeploying from a patched base image tag — not patching packages inside a running container, which doesn't survive the next deploy anyway. Log4Shell (CVE-2021-44228, CVSS 10.0, disclosed December 10, 2021) didn't live in a container base image itself, but the remediation pattern it forced — rebuild every image that bundled a vulnerable log4j-core jar, push, redeploy, verify — is exactly the pattern base-layer CVEs demand. The curl SOCKS5 heap buffer overflow (CVE-2023-38545, disclosed October 11, 2023 alongside curl 8.4.0) is a closer analogy: because curl ships in nearly every Debian- and Alpine-based image, that single CVE touched thousands of downstream container images simultaneously, and the only real fix was pulling the updated base tag and rebuilding — in-place apt-get upgrade inside a running container doesn't get you a compliant, reproducible artifact. The runc container runtime CVE (CVE-2024-21626, disclosed January 31, 2024, allowing container breakout via a leaked file descriptor) followed the same shape: patch the runtime, rebuild the base, redeploy the fleet. In every case, the teams that recovered fastest had CI pipelines that automatically rebuilt on a base image tag change rather than pipelines that required someone to notice a CVE, find every affected Dockerfile, and manually bump versions.

Should you pin base image tags to a digest or keep tracking "latest"?

Pin to a specific SHA256 digest for build reproducibility, but pair that pin with automation that proposes an update the moment a patched digest is published — pinning without a refresh mechanism just delays today's CVEs into next quarter's incident. Floating tags like node:20 or python:3.12-slim are dangerous because the image behind that tag changes silently; a build that passed your scan on Monday can pull a newly-vulnerable layer on Thursday with zero code change to blame. Fully pinned digests solve that reproducibility problem but introduce the opposite failure mode: teams pin once during a migration and never revisit it, so an image that was clean in January 2024 is running six-month-stale glibc and openssl by mid-year. The fix isn't choosing between the two — it's pinning digests as the default state while running an automated job (Renovate, Dependabot, or a purpose-built container patching tool) that opens a pull request every time the upstream maintainer publishes a new patched digest for your pinned tag family.

How Safeguard Helps

Safeguard cuts base image CVE noise down to what actually matters by running reachability analysis against your running services, so a finding in libxml2 or openssl gets flagged as exploitable only when your application's call graph actually reaches it. Griffin AI, Safeguard's remediation engine, cross-references that reachability signal with available upstream patches and opens auto-fix PRs that bump the base image tag or swap in a minimal/distroless equivalent, with the CVE delta shown inline for reviewer context. Safeguard generates SBOMs at build time and ingests existing SBOMs from your CI pipeline, so every base image layer — including transitive OS packages you never explicitly pulled — is tracked against NVD and vendor advisories the moment a new CVE is published. The result is a remediation queue ranked by actual exploitability instead of raw CVSS score, with a PR already drafted for the fixes that matter.

Never miss an update

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