Dev.to Security 🔐 Cybersecurity 👁 0 📖 7 min read

After the eMudhra CA compromise: audit your own TLS stack in 30 minutes

On 17 October 2026 a researcher told the root programs that the private keys of eight eMudhra intermediate certificate authorities were compromised, and proved it the hard way: they signed a short message with each of th

On 17 October 2026 a researcher told the root programs that the private keys of eight eMudhra intermediate certificate authorities were compromised, and proved it the hard way: they signed a short message with each of the eight keys and attached the signatures. Anyone can verify those with OpenSSL. The keys had been recoverable since April 2025.

Browsers were fixed within days. Firefox and Chrome blocked the intermediates on the 17th, Microsoft platforms on the 20th, Apple products on the 22nd. The thing you browse with is, by now, fine.

The machines you administer are another matter. This is the checklist for those, plus the parts of the story that decide what you should check. Everything about the incident comes from the incident page, which we re-read the day this went out. We ran the four test endpoints from step 1 on one Ubuntu 26.04 box of ours, and all four failed with a certificate error, which is the result you want. We did not audit every runtime on it, and the steps below are not a record of us doing that.

What actually broke

The eight intermediates chain to roots trusted by the Mozilla, Microsoft, Apple and Chrome root programs. Whoever holds one of the private keys can mint a certificate for any domain, and a client that still trusts the intermediate will accept it.

The cause is worth remembering. The CA generated those keys incorrectly, so they can be recovered from the certificates themselves. That was true from the day the certificates were created, April 2025. Certificate Transparency logs hold no entries for these intermediates in that window, so clients that enforce CT, which means the major browsers, could not have been attacked through them. Clients that do not check CT are another story, and the incident page says plainly that nobody knows whether attackers used the flaw against them.

The CA revoked the intermediates, and the root programs pushed that revocation through their own channels: OneCRL and CRLite for Mozilla, CRLSets for Chrome. That is how a browser stays protected without a new build. A great many clients never look at any of those channels.

Two layers of trust, and only one was hit

Most self-hosting guides lump these together, so it helps to pull them apart.

Your certificates are fine. If Caddy, Traefik, acme.sh or certbot gets your certificates from Let's Encrypt, nothing about them changed. The eMudhra intermediates are not in that chain, and the ACME protocol and your account keys were not involved. Renewal keeps working.

What your tools trust is the exposed part. Every outbound HTTPS request your server makes (git pull, curl to an API, a package install, a container pull, a webhook) is only as trustworthy as the certificate store on that machine. If the store still holds the bad intermediates, someone on the network path can pose as GitHub, your package mirror or any API, and the client will accept it without a murmur.

That is the whole risk, and most of it sits in software that is not a browser.

Why "update the OS" is not the whole fix

Two things set browsers apart from everything else here.

Revocation first. Browsers get these intermediates revoked through OneCRL, CRLite and CRLSets. curl, git, Go binaries and most language runtimes use none of those, so for them the only cure is a trust store that no longer contains the intermediates.

Then Certificate Transparency. Browsers refuse certificates that were never logged. Most non-browser clients do not care. The incident page runs two extra test hosts for this: their certificates were never logged to CT, so a client that connects to them trusts the bad intermediates and does not enforce CT either. That combination is the one an attacker can use without anybody noticing.

So the job is not one update but one per program that ships its own trust store or inherits one.

The 30-minute audit

Run it on every server, CI runner and dev machine you touch. Each step is a command or two.

1. Update the system CA bundle

Debian and Ubuntu:

sudo apt update && sudo apt upgrade -y ca-certificates

RHEL, Fedora, Alma, Rocky:

sudo dnf upgrade -y ca-certificates

Alpine:

sudo apk upgrade ca-certificates

The fixed package versions are listed on the incident page. For Debian 12 it is 20250419~deb12u2, for Ubuntu 24.04 20260816ubuntu0.24.04.1, for Alpine 20261020-r0. Compare with dpkg -l ca-certificates or apk info ca-certificates, then run the real test. A certificate error means this client does not trust the compromised intermediates. A successful response means you are still exposed:

curl https://dv-g3b-emudhra.we-have-a-problem.com/
curl https://dv-g3b-aaa-emudhra.we-have-a-problem.com/
curl https://dv-g3b-noct-emudhra.we-have-a-problem.com/
curl https://dv-g3b-aaa-noct-emudhra.we-have-a-problem.com/

If you run Java from Debian or Ubuntu packages, do one more thing: the system update does not refresh the Java keystore, because of a long-standing bug in ca-certificates-java. The incident page's advice is to recreate it as root:

rm -f /etc/ssl/certs/java/cacerts && update-ca-certificates --fresh

2. Fix the bundles the system update never touched

Several runtimes carry their own copy and will not notice your ca-certificates upgrade.

Python's requests reads the certifi bundle, not the system store. The fixed release is certifi 2026.10.19, and pip itself is fixed in 26.2.2:

pip install --upgrade certifi pip

Node.js ships its own root store rather than reading the system one by default. The patched releases are 26.12.1, 24.22.1 and 22.23.4, so a long-lived Node service needs a newer Node, not a newer ca-certificates. Go on Linux does read the system store, but the project also shipped fixes in 1.27.3 and 1.26.10, and Rust programs that bundle webpki-roots need a rebuild against a patched crate.

When we looked, certbot and containerd were still listed as pending on the incident page. Check the page again before assuming your version is covered.

Whatever reads the system store still has to be restarted. A service that started before the update keeps the old store in memory until you restart it.

3. Pull or rebuild your container images

An image freezes its CA bundle at build time. If it was built before the distro shipped the removal, it trusts the intermediates no matter how patched the host is.

docker pull debian:bookworm   # or your base image
docker compose build --pull   # if you build your own

The part that bites: an old tag never gets rebuilt. If your Dockerfile still says node:20 or golang:1.23 or alpine:3.19, those lines have reached end of life, and pulling again returns the same stale bundle. Move to a supported tag. For images you do not build, check that the publisher has released a new tag after 20 October. This is the step most people skip and the one that protects the containers.

4. Update Electron apps wherever you develop

Electron apps pick up the fix only when they are rebuilt on a patched Electron release (44.9.1, 43.8.1 and 42.11.16 are the fixed lines). Updating the OS or the browser does nothing for them. Slack, Discord and anything else on that runtime, on your desktop and on CI runners, needs its own update.

Apple developer tools were a special case: the git, curl and python3 that ship with macOS still trusted the compromised certificates as of 28 October, and macOS 27.2 undid the fixes some people had applied by hand. On a Mac, test the tools directly with the curl commands above rather than trusting the OS version.

5. Look at the runners your CI really uses

A hosted runner, a self-hosted agent or a build container is a supply-chain target here. If an attacker can pose as the package registry, whatever that runner builds is theirs to modify. Check that the runner image is current and that the pinned base image from step 3 has been refreshed. GitLab runner 19.5.1 is the fixed release if that is your runner.

What this does not touch

This topic was trending because of Let's Encrypt's multi-perspective validation, where domain control is checked from several network vantage points so that a BGP hijack on one path cannot fool the CA into issuing for a domain it should not. It is a real defence, and it lives at the issuance layer: it protects the moment a certificate is requested.

The eMudhra problem sits at the trust layer. A CA's keys were compromised, which has nothing to do with how domains were validated, so multi-perspective validation would not have caught it. The two are easy to confuse. "Let's Encrypt validates from several places" is not a reason to skip the audit above.

For your own ACME stack it comes down to three things. Your certificates are unaffected and renewals keep running. Your clients are the risk, and only a trust-store update fixes them. And Caddy or Traefik terminating TLS with your own certificate is fine, while the git, curl, docker and language runtimes on the same server are the attack surface.

Where to run the audit

The stack you are checking is most likely a small VPS or a container host, and the steps above assume a shell on it. If you are still choosing the machine, we benchmarked the budget tier and wrote it up in the VPS comparison. The ACME setup that serves this site, Caddy and Docker behind a single hostname, is in the production Caddy guide. The legacy-machine case, where certbot will not install and you take renewal back with acme.sh, is the one most self-hosters inherit sooner or later.

Originally published at news.rozoom.app.

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.