Browsers Got a Push. Your Code Got Nothing.

The key was 4.
Not a key derived from 4, or a key with a weakness that makes it 4-ish. Eight of the private keys behind intermediate CA certificates issued under eMudhra's emSign roots were integers below 256, which on a P-384 curve means a value that's zeros apart from the last byte. Anyone with the public certificates could recover them by generating the first 256 private keys and comparing. Scott Helme did it in under a second on a laptop. Mozilla's investigator noted that local agents managed it first time from a one-line prompt claiming to be doing a CTF, which tells you everything about where the bar was.
It's a good number, and every write-up will lead with it. But this isn't a post about entropy, or about eMudhra's key ceremony, which I'll come back to when the final incident report is out. It's about who got the fix, and how. The browsers had it within hours. Most of the software you run in production had to wait for you.
The browsers fixed it by not asking the CA
The report reached the Chrome Root Program at 18:05 UTC on Saturday 17 October, from a researcher Mozilla's bug names only as Wayne. Chrome's CRLSet blocked the six TLS intermediates at 19:45. Mozilla received the report, forwarded by Chrome, at 20:06, recovered the keys independently at 21:48, and had its OneCRL entry complete at 22:59.[1] Apple and Microsoft followed over the next three days. By the time the story reached the technical press, browser users had been protected for most of a weekend.
None of that involved a browser release. CRLSet and OneCRL are lists of revoked or distrusted certificates that the browsers fetch on their own schedule, out of band, independently of software updates. They exist because the original revocation mechanisms failed in practice. Online CRL and OCSP checks were slow, soft-failed whenever the network was unhelpful (which is precisely when an attacker would make it unhelpful), and told the CA which sites you were visiting. Adam Langley's 2012 post explaining why Chrome stopped relying on them is still the clearest account, and the web has moved further that way since, with Let's Encrypt switching off OCSP entirely in 2025.
So the browsers solved revocation by not asking the CA at all. They ship a list, and they've built the pipe to ship it through.
Everything else trusts a file
Most other TLS clients don't have the pipe. They trust a bundle of root certificates on disk, build a chain to one of them, and stop. They don't fetch CRLs, they don't make OCSP requests, and they don't pull a blocklist from anyone. Helme's tests are the cleanest illustration: on Linux and macOS, curl and Node accepted a chain through one of the compromised intermediates. Windows CryptoAPI accepted it too, unless revocation checking had been switched on.
That's not a bug in curl. It's the design, and it means that an intermediate can be compromised, revoked, logged, and blocked in every browser on earth while your service's HTTP client goes on trusting it, because nothing about the event reached the file it reads.
The file comes from somewhere different depending on the client, and this is where it gets operational. Python's requests and urllib3 use certifi, a copy of Mozilla's root store packaged for PyPI. So do the AWS CLI and boto, through their own bundled certifi. pip trusts the system store and certifi together, so its vendored copy still counts. Terraform, kubectl, and most cloud-native tooling are Go, and read the system bundle on Linux. git, package managers, and anything built on OpenSSL read the system bundle too, which in a container means whatever version of ca-certificates was in the base image the day it was built.
None of which is cause for panic. Exploiting any of this needs an attacker on the network path between your client and the server it's talking to, and nobody has reported an attack. Chrome's root programme says no TLS certificate chaining to the six intermediates had been logged before the block. That's a reason for calm. It isn't a reason to leave it.
Go put the revocation in the binary
The most instructive response came from Go. Go 1.27.3 and 1.26.10 filter out any chain that passes through one of the eight keys, matched by a SHA-256 hash of the public key, inside crypto/x509 itself (CVE-2026-111943). The filter runs inside Verify, so it applies whether you use the system roots or supply your own pool.
Someone in the issue called it a bad precedent for a standard library to make trust decisions. The reply is the heart of this post. The keys are compromised and the certificates revoked, so it isn't a policy judgement about a CA. And, in a line I'd like to see framed in every platform team's office, "the state of Linux root stores (a pile of PEM files behind a dumpster) is such that the revocation of these intermediates has no way to propagate to applications, short of hardcoding them in code like this." The Go team's position is that it wants to invest in something less ad hoc, which needs coordination across the whole stack and won't be quick.
Node reached the same conclusion a day later. Its 21 October releases (26.12.1, 24.22.1, and 22.23.4) added a mechanism to reject chains containing blocked public keys, with the eight eMudhra keys as its first entries, alongside a refresh of the bundled root store. Two of the most widely used runtimes now carry a hardcoded blocklist, because there was nowhere else to put it.
The consequence nobody puts in the headline is that the fix now lives in the compiled artefact. Upgrading Go on your build hosts changes precisely nothing about the Go binaries already running. Each one has to be rebuilt with the patched toolchain and redeployed. The same goes for every container image whose Node runtime came from a base image built before the 21st.
The blunt instrument
On 20 October, by which time several people had recovered the keys independently and some had said so in public, Mozilla removed trust in the emSign ECC Root CA - G3 entirely, and Chrome committed to the same. Ubuntu's and Debian's ca-certificates updates that day did the same thing, and Debian's removed the Comodo AAA root that had cross-signed the intermediates as well. certifi had already gone further, two days earlier.
Removing the root is the only lever that reaches a client which can't revoke an intermediate, because if you can't distrust the link you have to distrust the anchor. It's also a blunt one, and certifi's releases show how blunt. Version 2026.10.18 removed every emSign root. Version 2026.10.19 restored the ones that weren't affected, on the same day eMudhra announced it would move about 30,000 certificates onto its RSA root. Anyone who'd pinned the first release, which is the responsible thing to do with a security fix, had pinned an over-correction that would have broken those sites as they moved. That's revocation at the speed of PyPI releases, decided by a handful of volunteers on a Monday.
Why your copy is frozen
So the trust store reaches a running service through three layers: the language package (certifi, pinned in your lockfile), the operating system's bundle (ca-certificates, pinned by the image digest), and the runtime itself (Go's crypto/x509, or Node's blocklist, compiled or baked in). Every one of those pins is good practice. I've spent most of a series on this site arguing for them. And every one of them means the fix doesn't arrive on its own.
I found this out the ordinary way. When the certifi advisory landed, most of my own repositories pinned a vulnerable version, and every service that deploys needed a bump, a rebuild, and a redeploy. The pinning made it visible: a search across lockfiles found every affected repository in seconds, and the bump PRs said exactly what changed. It didn't make it fast. A browser's fix arrived while its user was asleep. Mine arrived when I'd reviewed and merged every PR and watched each image roll out, which is to say a couple of days after the browsers', and I was paying attention.
A revocation drill
What to do now is straightforward. Upgrade certifi to 2026.10.19 or later everywhere it appears, including in vendored copies. Update ca-certificates and rebuild every image that carries it. Rebuild every Go binary with 1.27.3 or 1.26.10, and move Node services to the 21 October releases. Then test, from inside a running container rather than from your laptop, against Dennis Jackson's test page, which serves a chain through one of the compromised intermediates. If that connection succeeds, you're not done.
What to rehearse is more interesting. Could you answer, within an hour, which of your deployed artefacts trust a given root? Could you rebuild and roll every service in a day if you had to? Do any of your services only talk to a fixed set of partners, where trusting a narrow set of roots instead of the whole public web would have made this a non-event? And what about the things you can't rebuild: the appliance, the firmware, the vendor's container you run but don't build, whose bundle will go on trusting these intermediates until they expire in 2039?
Revocation is a release
The browsers have a revocation channel because they paid for one, years ago, after the online checks failed them. Servers never did. For a server, revocation is a release: an update, a rebuild, a redeploy, repeated for every artefact you run. That's not going to change soon, so the useful thing is to treat the trust store as code you ship, and to measure how long a release of it takes before the next set of keys turns out to be small integers.
The times and dates here are approximate. This moved very fast, over a weekend and across several organisations' timelines, and the public record still disagrees on some of them by minutes or, for the downstream fixes, by a day. ↩︎