The EU Cyber Resilience Act’s 24-hour reporting duty starts 11 September 2026. What it actually requires
remediationCVEbase images

CVE patching in Kubernetes: fix versions, base images, and what "fixed" means

Why a CVE in a running pod is patched by a chart bump or a base-image rebuild rather than apt-get, how distro backports make version numbers lie, what "no fix available" and "won’t fix" really mean, and how to sequence the work.

StackRadar Team

· 11 min read

On a server you patch a CVE with a package upgrade. In Kubernetes you cannot — the container is disposable and the package manager inside it is a build-time tool that happens to still be there. The fix for a CVE in a running pod is always a new image, and getting from "CVE-2025-XXXXX affects libssl3" to a new image running in the right place is where most of the work, and most of the confusion, is. This post goes through it: what a fix version actually means, why distro version numbers lie, what "no fix" and "won't fix" mean, and how to sequence the work so the list shrinks.

The unit of fix is the image, deployed by something

A finding lives in an image — a specific digest that contains a specific package version. That image runs because something deployed it: a Helm release pinning a chart version, an ArgoCD application pointing at a Git path, or a Deployment someone applied by hand. The fix therefore has two halves, and the second is the one scanners forget:

  1. Produce an image without the vulnerable package version. Rebuild yours on a newer base; for someone else's, find the tag that has it.
  2. Change the thing that deploys it so the new digest is what the kubelet runs — a chart bump, a values change, a Git commit — and confirm the running digest moved.

Roughly half the images in a typical cluster are third-party — ingress controllers, cert-manager, monitoring agents, CSI drivers, the mesh — and for those, half one is "wait for the maintainer" and half two is a chart version bump. This is why StackRadar attributes every finding to its Helm release and ArgoCD application: the actionable row is "upgrade ingress-nginx from 4.11.2 to 4.12.1", not "libcurl in 14 images".

What "fixed in" means

An advisory says the vulnerability is fixed in a version. Which version depends entirely on the ecosystem, and comparing across ecosystems is the single most common way to get a wrong answer.

Ecosystem"Fixed in" looks likeMeaning
Upstream / language (npm, PyPI, Go)4.19.2 → 4.20.0A new upstream release. Ranges are exact in OSV.
Debian / Ubuntu3.0.11-1~deb12u2 → 3.0.15-1~deb12u1Backported into the stable branch; upstream may be at 3.3
Red Hat / UBI1.1.1k-7.el8_6 → 1.1.1k-12.el8_9Same upstream version, fix in the release suffix
Alpine3.3.2-r0 → 3.3.2-r1Package revision bump, tracked in the aports secfixes block
Go module in a binaryv0.24.0 → v0.25.0Requires whoever built the binary to rebuild it

The consequence: a scanner that compares your Debian openssl 3.0.15-1~deb12u1 against upstream's "fixed in 3.3.2" will report a vulnerability that Debian fixed months ago. Good matchers evaluate distro packages against the distro's own security tracker, which is why the PURL carries the distro (pkg:deb/debian/openssl@3.0.15-1~deb12u1?distro=debian-12) and why version-range evaluation has to understand Debian's epoch-and-tilde ordering, RPM's release field and Alpine's -rN. When two scanners disagree about a distro package, this is nearly always where.

"No fix available" and "won't fix"

A large share of findings on any Debian- or Ubuntu-based image carry no fixed version. Three different things hide behind that:

  • Not yet. The advisory is new and the distro has not shipped a backport. Usually days to a few weeks for anything serious; watch for it.
  • Deliberately not. Debian marks low-impact issues no-dsa ("minor issue", fixed in the next point release if at all); Red Hat marks some "Will not fix" or "Out of support scope"; Ubuntu has "negligible". The vulnerable code is often unreachable in their build. The finding is real and unactionable by upgrade.
  • End of life. The distro release, or the language runtime, no longer receives security fixes at all. Every future CVE in it will be "no fix". The only remedy is moving to a supported base; StackRadar flags EOL images separately from awaiting-fix ones for that reason, and never guesses — an unknown release is reported as unknown, not as EOL.

For the "deliberately not" class, the honest options are: change to a base image that does ship the fix (Alpine or a distroless/Wolfi image often does), mitigate (the vulnerable feature is off), or record an accepted risk — with an owner, a reason and an expiry, so it is revisited rather than forgotten. Suppression without an expiry is how a cluster ends up with three-year-old "temporary" exceptions.

Base images do most of the work

On most application images, 80–95% of findings are in the OS layer, and they are fixed by rebuilding on a newer base. Three practices make that cheap:

  1. Pin the base by digest and bump it on a schedule. FROM debian:12-slim@sha256:… with Renovate or Dependabot opening the bump. A fix arrives as a one-line reviewed change; builds stay reproducible.
  2. Use a smaller base. Fewer packages is fewer findings and fewer "won't fix" rows. Distroless, Alpine, Wolfi-based and UBI-micro images all cut the OS layer by an order of magnitude; the trade is debuggability and, for Alpine, musl.
  3. Rebuild on a schedule even without code changes. An image built six months ago has six months of unpatched OS packages. A weekly rebuild of every image on its pinned-and-bumped base closes most of the long tail without anyone reading a CVE.
Dockerfile
# Pinned by digest; Renovate bumps the digest when a new 12-slim is published.
FROM debian:12-slim@sha256:<digest-of-the-current-12-slim>
# No apt-get upgrade here: the fix comes as a base-image bump, not a build-day surprise.
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \
 && rm -rf /var/lib/apt/lists/*
apt-get upgrade in the Dockerfile does fix CVEs, and many teams do it. The cost is that the same Dockerfile produces a different image on Tuesday than on Monday, and the SBOM you attested on Monday describes an image nobody runs. Digest-pinning moves the same fix into a reviewed change.

Sequencing the work

The list is long; the order matters more than the speed. A sequence that works:

  1. Anything in CISA KEV, in any image, this week. It is a short list.
  2. Anything with EPSS ≥ 0.1 and a fix, sorted by CVSS. Still short.
  3. Third-party chart bumps that close the most findings per change. One ingress-nginx upgrade typically closes dozens.
  4. Base-image bumps for your own images, oldest base first. This is where the long tail goes.
  5. The no-fix residue: base-image change, mitigation, or a recorded, expiring exception.

StackRadar's Radar Score is the first two steps as a sort order, and the release-level view is the third: every Helm release with the findings it carries and the images it deploys, so "upgrade cert-manager" is one row. What it deliberately does not do is claim a bump is safe — it tells you what the move buys, and reading the changelog is still your job.

Confirming it is actually fixed

The Deployment says :1.42.1. The pod might still be running :1.42.0 — a failed rollout, a paused one, an imagePullPolicy surprise, a tag that was retagged. The only thing that confirms a fix is the running digest changing:

bash
kubectl get pods -n checkout -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.containerStatuses[0].imageID}{"\n"}{end}'

A scanner keyed on running digests closes the finding when the vulnerable digest stops running and not before — which is also why StackRadar's issue tracking is verified only by the scanner's next sweep, never by someone clicking "done". The fix is real when the kubelet says so.

Frequently asked questions

How do you patch a CVE in a Kubernetes pod?

You do not patch the pod; you replace the image. Containers are immutable in practice — a package upgrade inside a running container is lost on the next restart — so the fix is a new image that contains the fixed package, deployed by changing the tag or chart version the workload references. For your own images that means rebuilding on a newer base image; for third-party workloads it means bumping the Helm chart or the image tag the chart pins.

What does "fixed version" mean for a CVE?

The first version of the specific package, in the specific ecosystem, that contains the fix. It is not necessarily a new upstream release: Debian, Ubuntu, Red Hat and Alpine backport fixes into their existing version and bump a suffix, so the fixed version of OpenSSL on Debian 12 might be 3.0.15-1~deb12u1 while upstream is 3.3. Comparing your package against the upstream version number will give the wrong answer.

What does "no fix available" mean?

That the ecosystem has not yet published a version containing the fix, or has decided not to. On distributions this is often deliberate: Debian marks low-impact issues "no-dsa" and Red Hat marks some "Will not fix" when the vulnerable code is unreachable in their build. In those cases the finding is real but unactionable through a package upgrade; the remedies are a different base image, a mitigation, or an accepted-risk record with an expiry.

Should I use apt-get upgrade in my Dockerfile to fix CVEs?

It works and is common, but it makes builds non-reproducible: the same Dockerfile produces different images on different days. The more durable answer is to pin the base image by digest and bump it on a schedule (Renovate and Dependabot both do this), so a fix arrives as a reviewed change to one line rather than as a silent difference between builds.

How do I know a CVE is actually fixed in production?

Check the running digest, not the manifest. The kubelet reports the image digest each container is running in status.containerStatuses[].imageID; if it has not changed, the fix has not landed regardless of what the Deployment says. A cluster scanner that keys on running digests, such as the StackRadar scanner or Trivy Operator, closes the finding only when the vulnerable digest stops running.