October 25, 2026
The CA bundle broke it, not the patch
Key takeaways

By Echo
6 min read
Key takeaways
- The patch is almost never what breaks โ six environmental failures account for nearly every broken migration, and five of them surface in the first thirty seconds.
- A backported fix leaves the version contract intact, which is exactly why it breaks less than the dependency upgrade teams imagine when they hear "remediation".
- One service, one changed line, one afternoon settles a compatibility argument more cheaply than any amount of planning โ and the worst service is the right one to pick.
- Record every fix from the pilot, because the same two or three apply across the estate and turn the second migration into a twenty-minute job.
- Backports create a version-string problem downstream, so insist on VEX and a complete SBOM rather than suppressing findings and relocating the argument to an audit.
The CA bundle broke it, not the patch
Every team that stalls on container remediation tells us a version of the same thing: we know the CVE is real, we are just not confident the fix won't break something. It is a reasonable fear and it is also, in our experience, aimed at the wrong target. When a base image swap breaks an application, the cause is almost never the patched code. It is the environment the patch arrived in.
The useful part: that environment breaks in a small number of ways, and the list barely changes between stacks.
The failure list is shorter than you think
Here is what actually goes wrong when a service moves onto a minimal or hardened base image, roughly ordered by how often we see it:
1. The CA bundle moved. This is the big one, and it is the one that looks catastrophic. The application looks for trusted roots at a path the previous image used, the new image keeps them somewhere else or expects them mounted, and every outbound TLS connection fails simultaneously. It looks like the network died. It is a path.
2. The entry point assumed a shell. Your ENTRYPOINT is a shell wrapper, or your container relies on an init system to reap children. A distroless-style image ships neither. The container exits immediately with an error that does not mention shells.
3. The health check shells out. The service is fine. The readiness probe runs curl, the debug sidecar runs ps, the startup script runs bash, and none of those binaries exist any more. Kubernetes marks a perfectly healthy pod as failing.
4. Locale data is gone. Date parsing changes, string sorting changes, log timestamps shift. This one is nasty because it does not crash โ it produces subtly wrong output that surfaces three days later in a report.
5. Non-root by default meets a root-owned path. The hardened image runs as an unprivileged user. A volume mount, a cache directory or a log path was created assuming root. Permission denied on startup.
6. libc mismatch. A binary built against glibc on an image shipping musl, or a dynamically linked binary on an image with no loader. Fails instantly and unmistakably.
Six items. We have yet to see a migration blocked by something that wasn't on this list, and five of the six announce themselves in the first thirty seconds of the container's life.
Why the patched library itself rarely breaks anything
Because a backported fix is specifically designed not to.
Backporting means taking the security fix from a newer upstream release and applying it to the version you are already running. The patch lands; the version does not move. Your function signatures, default values, configuration keys and documented behaviour all stay where they were. There is no deprecation to work around and no migration guide to read, because from the application's point of view nothing has changed except that an exploit no longer works.
Compare that to the thing teams usually imagine when they hear "remediation": bump the dependency, find a breaking change in a minor release, discover three transitive dependencies now conflict, spend a sprint on it. That is a real cost and it is why backlogs grow. It is also not what a backported fix asks of you.
There is a genuine trade-off, and it is worth being honest about it. Somebody has to maintain that patch, because upstream will not. Every backport is a divergence that needs re-applying each time the component moves. If you do it yourself, that maintenance and its test matrix are permanently yours.
Find it in an afternoon
The way out of a six-month compatibility debate is to stop debating and run one experiment. What we suggest:
the entire change
- FROM python:3.12-slim
- FROM :3.12
Change nothing else. Then:
- Build and run it. Watch the first thirty seconds of logs. TLS failures, missing entry points and permission errors all show up here, loudly.
- Run your existing test suite, unmodified. You are testing whether the behavioural contract held. If the suite needed changing, that is itself the finding.
- Diff the SBOMs, not the tags. Tags tell you what someone called it. An SBOM tells you what is in it. If your image provider ships one โ Echo ships an SBOM, provenance and VEX with each image, per echo.ai/product โ this is a two-minute comparison rather than an investigation.
- Write down every fix. This is the bit people skip and regret. The same two or three fixes will apply across most of the estate, and the second service takes twenty minutes instead of an afternoon.
Pick your worst service for this, not your easiest. A clean pilot on a simple service convinces nobody. A pilot on the service with four hundred findings and a nervous owner produces the only evidence that will move the rest of the organisation.
The full step-by-step is on the Echo blog in migrating from public base images to hardened container images, which covers the same ground with more of the registry and rollout detail.
The scanner problem nobody warns you about
Here is the thing that will catch you after the migration succeeds.
Most scanners identify vulnerabilities by matching installed package versions against a database. A backported patch changes the contents of a package without changing its version string. So the scanner looks at your genuinely fixed package, sees the old version number, and reports the CVE as present.
Your image is patched. Your scan says it isn't. Nothing is broken.
You cannot fix this by patching harder, and you should not fix it by suppressing the finding โ that just relocates the argument into someone else's audit. The honest mechanisms are:
- VEX. A Vulnerability Exploitability eXchange document travels with the artifact and states, per CVE, whether this build is affected and why. CISA's minimum requirements define the status values, and using non-standard ones defeats the point since the receiving tool can't parse them.
- A complete SBOM, transitive dependencies included, so the other side can verify rather than infer.
- Build provenance, so the SBOM is evidence instead of a claim. SLSA Build Level 3 is the common bar here.
If you are evaluating an image provider, this artifact set is a better question than the CVE count on their marketing page. The count is a snapshot of a database. The artifacts are what let you defend the image in front of a customer six months from now.
What we actually think
The regression fear is legitimate and the industry has handled it badly by treating it as an objection to overcome rather than a question to answer. It deserves an answer, and the answer is cheap: one service, one afternoon, one changed line.
What that afternoon usually reveals is that the fix was two config changes โ a CA path and an entry point โ and that the thing blocking a thousand repositories was a conversation nobody had bothered to settle with evidence. The patch was never the risk. The environment around it was, and the environment breaks in six known ways.
More on the remediation side of this on the Echo blog: vulnerability patching.
FAQ
Is a backported fix as safe as upgrading?
For the specific vulnerability it addresses, yes โ the same code change is applied to an older release. What you do not get is everything else in the newer version: other security fixes, performance improvements, new features. If several CVEs affect one component, verify each is individually addressed rather than assuming one backport covered the set.
Why does my scanner still flag a CVE I patched?
Scanners typically match installed package version strings against a vulnerability database. A backported patch alters package contents but leaves the version unchanged, so the tool concludes the vulnerable release is present. This is the tool working as designed. The resolution is a VEX document declaring this build unaffected, supported by an SBOM the reader can verify independently.
What breaks most often during a base image migration?
Certificate authority bundle paths, by a wide margin. The application looks for trusted roots where the old image kept them, finds nothing, and every outbound TLS call fails at once โ which presents as a network outage rather than a configuration issue. After that come entry point assumptions, missing shells used by health checks, and absent locale data.
How long should a compatibility pilot take?
For one service, an afternoon. Change only the FROM line so any failure is attributable, start the container, and watch thirty seconds of logs โ most failures are immediate and loud. Then run the existing test suite unchanged. Record every fix you make, because the same two or three will apply across most of the remaining estate.
Who maintains a backported patch long term?
Whoever produced it. Upstream maintainers do not support fixes applied to older releases, so the divergence has to be re-applied each time the component changes. If you backport in-house, that work, its test matrix and its on-call burden are yours permanently. If a vendor does it, ask how long a given version keeps receiving fixes.