By clicking “Accept”, you agree to the storing of cookies on your device to enhance site navigation, analyze site usage, and assist in our marketing efforts. View our Privacy Policy for more information.
18px_cookie
e-remove
Blog

eMudhra Intermediate CA Compromise: The Trust Stores You Patched, and the Ones You Didn't Know You Had

Eight eMudhra intermediate CA keys were compromised, six of them letting anyone who holds them impersonate any website or service. Browsers are already protected. Many non-browser clients are not, and some of the exposed trust stores are buried inside dependencies no patch list covers.

‍

Written by
Peyton Kennedy
Peyton Kennedy
Cris Staicu
Cris Staicu
Published on
October 21, 2026
Updated on
October 21, 2026

TL;DR

On October 20, 2026, the private keys of eight eMudhra intermediate CA certificates were disclosed as compromised. The keys were generated incorrectly, so anyone can recover them from the certificates themselves. The intermediates chain to roots trusted by the Mozilla, Microsoft, Apple, and Chrome root programs, both directly and through Sectigo cross-signatures. A party holding one of the six compromised TLS issuing CA keys can issue a certificate for any hostname that affected clients will accept.

Browsers are protected. Firefox and Chrome have blocked the intermediates since October 17, Apple since October 19, and Microsoft since October 20. The CA has also revoked them. The remaining exposure lies in software that doesn't consume browser revocation data, doesn't enforce certificate transparency, and decides what to trust from a bundled or system root store.

That exposure splits into two parts:

  • The known risk. Operating system packages, language runtimes, and certificate bundle packages that ship root stores. These have published fixes with version numbers. Patch them.
  • The unknown risk. Libraries that carry their own frozen copy of a root store inside the package. They declare no dependency on a certificate bundle, have no fix on any list, and stay exposed after every system update. Some report 29 packages being affected; we found 25 fairly popular packages trusting a root in the compromised chains (2 examples below), 11 of them already patched.

Three things stand out about this incident:

  • The exposure window started in April 2025. The keys were recoverable from the day the certificates were issued. There are no certificate transparency log entries for these intermediates in that period, which rules out attacks against CT-enforcing browsers. It does not rule out attacks against clients that skip CT, and most non-browser clients skip it.
  • Revocation does not reach most of the exposed software. The CA has revoked the intermediates, but default TLS stacks in Python, Ruby, Go, Node.js, and most command-line tools do not check revocation. For them, only a trust store update removes the risk.
  • Patching the system does not patch the application. A library that loads a trust store from inside its own package ignores the operating system's store. An organization can complete every published update and still run services that trust the compromised chains.

What happened

Each of the eight intermediates was created in April 2025 with a private key that can be derived from the certificate. Proof that the keys are in third-party hands was delivered to the root programs on October 17: a message signed with the private key of each intermediate. The disclosure site publishes the signatures, certificates, and public keys so anyone can verify them with OpenSSL.

The intermediates chain to four roots:

Root Operator Role in the chains
emSign ECC Root CA - G3 eMudhra Cross-signer
emSign Root TLS CA - G3 eMudhra Direct issuer
emSign Root CS CA - G3 eMudhra Direct issuer (code signing)
AAA Certificate Services Sectigo Direct issuer, through cross-signatures

AAA Certificate Services is the root to watch. It is a legacy Sectigo root, formerly published under the label Comodo AAA Services root, and it appears in a large number of trust stores. The Mozilla and Chrome programs have already removed it, but frozen snapshots of older root stores still carry it.

The disclosure lists the eight intermediates and their Sectigo cross-signs in impacted-intermediates.json, and the four roots in roots.json, both on the disclosure site at emudhra.we-have-a-problem.com. Mozilla's bug tracker tracks the technical cause in the initial incident report.

Why non-browser clients carry the risk

A browser that sees a certificate from one of these intermediates rejects it three ways: the intermediate is blocked by the browser's own revocation mechanism (OneCRL, CRLite, or CRLSets), the CA has revoked it, and the certificate has no certificate transparency proof. A typical non-browser TLS client applies none of those checks. It builds a chain, compares the top against its trust store, and accepts the connection if a trusted root anchors the chain.

That describes most server-side software: API clients, webhooks, package managers, CI jobs, service-to-service calls, agents, and command-line tools. For each, the only control that matters is the content of the trust store it loads. Two questions follow: which trust store does each client load, and does that store still trust any of the four roots above?

The known risk: published trust stores

These components ship root stores and have published fixes (see here for a continuously updated list):

Component Fixed in
Ubuntu ca-certificates 20260816ubuntu0.22.04.1, 20260816ubuntu0.24.04.1, 20260816ubuntu0.26.04.1, 20260816ubuntu0.26.10.1
Debian ca-certificates 20250419~deb12u2, 20250419+deb13u1, 20260816+nmu1
Alpine ca-certificates 20261020-r0
Fedora ca-certificates 2026.2.92_v9.0.319
Arch ca-certificates-mozilla 3.131.1-1
Chainguard ca-certificates 20260909-r3
NSS 3.131.1
curl CA bundle 2026-10-20
Python certifi 2026.10.19
pip 26.2.2
uv 0.13.4
Node.js 22.23.4, 24.22.1, 26.12.1
Go 1.27.3, 1.26.10
Electron 44.9.1, 43.8.1, 42.11.16
Rust rustls-webpki 0.103.16
Rust webpki-roots, webpki-root-certs 1.0.10
PHP composer/ca-bundle 1.4.16, 1.6.0
Elixir castore 1.0.22
Perl Mozilla::CA 20261020

FreeBSD ca_root_nss and Bun had fixes pending at the time of writing. The disclosure site keeps the current list.

These are tractable. Each is a named component with a version, so dependency and container scanning can find every place they resolve below the fixed version, including transitive dependencies. Most applications receive a certificate bundle package through an HTTP library rather than declaring it, so check resolved dependency trees, not manifests. Container base images need the same review: a host patched today does not fix an image built last month.

Two components need attention outside the table. The JDK ships its own cacerts keystore and is not on the fixed list, so treat Java services as unconfirmed until your JDK vendor publishes an update. And emSign Root CS CA - G3 is a code signing root trusted by Microsoft, so any system that verifies code signatures chaining to eMudhra needs a separate review.

The unknown risk: trust stores hidden inside packages

Some libraries do not rely on the system trust store or on a certificate bundle package. They ship a PEM file inside their own package and load it at runtime. That file is a snapshot of a root store taken when a maintainer last updated it, and it changes only when the library publishes a new release with a new file.

We examined two widely used HTTP client libraries and hashed every certificate in their bundled files against the four roots. Both trust AAA Certificate Services. Neither appears on any list of fixed software.

Pattern 1: a bundled store used as a fallback

A Python HTTP client library ships a 71-certificate trust store inside the package. At runtime, it chooses a trust store in this order:

  1. A file named in a library-specific environment variable
  2. A trust store locator package, if installed
  3. The certifi package, if installed
  4. The bundled file inside the library

The bundled file contains AAA Certificate Services. Its latest release still ships it, and the library does not declare a dependency on certifi. Whether an application is exposed therefore depends on what else is installed. An environment that also has certifi through another library uses certifi, which is protected once certifi reaches 2026.10.19. A minimal environment, a slim container image, or a function package with trimmed dependencies falls through to the bundled file and stays exposed regardless of any update to the operating system or to certifi.

The library reaches most applications transitively. Widely used cloud API client SDKs depend on it directly, so services that call those APIs carry it without anyone choosing it.

Pattern 2: a bundled store used by default

A Ruby HTTP client gem ships a 153-certificate trust store extracted from Mozilla's root list as of October 28, 2015. The library documents that it loads this file as its trust anchor at initialization, so it is the default for every client instance. The operating system store applies only when application code explicitly opts in by calling a method that switches to OpenSSL's default paths.

The bundled file contains AAA Certificate Services. In this pattern, updating ca-certificates, upgrading the language runtime, or installing a newer certificate bundle package changes nothing. The application trusts an eleven-year-old root list until its own code says otherwise.

Why these are hard to find

Hidden trust stores share properties that defeat the usual tooling:

  • No dependency signal. The library does not depend on a certificate bundle package, so dependency analysis has no trust store component to version-check.
  • No fixed version. When the latest release still ships an affected root, a version threshold cannot express "safe."
  • Behavior depends on the environment. In the fallback pattern, the same library version is safe in one environment and exposed in another, depending on unrelated packages.
  • The file is data, not code. A PEM file inside a package does not appear in a software bill of materials as a component.

The same pattern appears outside language packages. Places to look include vendored cacert.pem files committed to repositories, SDKs that embed root lists, container images that copy a bundle into the filesystem at build time, compiled binaries that embed a root store at build time, desktop applications built on embedded runtimes, and firmware or appliance images.

What to do now

Step 1: Patch the known components

Update operating system ca-certificates packages, rebuild container images from patched bases, and upgrade the runtimes and bundle packages in the table. Pin certifi>=2026.10.19 wherever Python runs, including environments that do not declare it, because adding it changes which store fallback-pattern libraries load.

Endor Labs tracks affected and fixed package versions using CVE-2026-111943, a vulnerability originally created for the Go runtime. We will search for bundled trust stores in packages across all ecosystems that contain any of the compromised certificates and add them as affected.

Full details for finding affected components with Endor Labs are in the appendix at the end of this blog.   

Step 2: Hunt for hidden trust stores

Scan source trees, installed environments, and container image filesystems for any file containing PEM certificates, and compare every certificate's SHA-256 fingerprint against the affected roots. Matching on fingerprints catches bundles regardless of filename or packaging.

Root SHA-256 fingerprint
AAA Certificate Services D7A7A0FB5D7E2731D771E9484EBCDEF71D5F0C3E0A2948782BC83EE0EA699EF4
emSign ECC Root CA - G3 86A1ECBA089C4A8D3BBE2734C612BA341D813E043CF9E8A862CD5C57A36BBE6B
emSign Root TLS CA - G3 7DD78D5F4F13459A83DFF9ABBBA62EDBAF6F2D102BF257FD712F4D9F2746ED8D
emSign Root CS CA - G3 F2EDAAFF3D9B701FB4D14C9F285F8C0BAD90FC3BCC1FA07D47F4718EDABA34A5
!/usr/bin/env bash

# Usage: ./find-affected-roots.sh <directory>

# Reports every bundled certificate that matches an affected root.

AFFECTED="D7A7A0FB5D7E2731D771E9484EBCDEF71D5F0C3E0A2948782BC83EE0EA699EF4

86A1ECBA089C4A8D3BBE2734C612BA341D813E043CF9E8A862CD5C57A36BBE6B

7DD78D5F4F13459A83DFF9ABBBA62EDBAF6F2D102BF257FD712F4D9F2746ED8D

F2EDAAFF3D9B701FB4D14C9F285F8C0BAD90FC3BCC1FA07D47F4718EDABA34A5"

tmp=$(mktemp -d)

grep -rlI -e '-----BEGIN CERTIFICATE-----' "$1" 2>/dev/null | while read -r f; do

  awk -v d="$tmp" '/BEGIN CERT/{n++} n{print > (d "/c" n ".pem")}' "$f"

  for c in "$tmp"/c*.pem; do

    [ -e "$c" ] || continue

    fp=$(openssl x509 -in "$c" -noout -fingerprint -sha256 2>/dev/null | cut -d= -f2 | tr -d ':')

    grep -qx "$fp" <<<"$AFFECTED" && echo "AFFECTED $fp  $f"

  done

  rm -f "$tmp"/c*.pem

done

rmdir "$tmp"

Extend the list with the eight intermediates from impacted-intermediates.json to catch bundles that include intermediates directly. A match on AAA Certificate Services means the bundle trusts the compromised chains through Sectigo's cross-signatures.

Step 3: Test what your clients trust

The disclosure site runs test hosts that serve certificates from the compromised chains. Run each client you care about against them, from the environment it runs in:

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

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

A certificate error means the client rejects the compromised intermediates. A successful connection means it still trusts them. Testing from the real environment matters because the fallback pattern behaves differently depending on what is installed. For offline review, chains.zip on the disclosure site contains every test host's chain.

Step 4: Override hidden trust stores

For each library that loads its own bundle, point it at a patched store instead of waiting for an upstream release. Most libraries that bundle a store accept an environment variable, a configuration option, or a client constructor argument that names a different file. For the default-load pattern, change application code to use the system store explicitly. Report each case to the library's maintainers, and to the disclosure site's list of affected software, which accepts additions.

Be careful with blanket distrust. Removing AAA Certificate Services from a store can break legitimate connections that still rely on legacy Sectigo chains. Replacing an outdated bundle with a current, patched one is safer than deleting individual roots by hand.

Step 5: Assess past exposure

The keys were recoverable from April 2025. For clients that do not enforce certificate transparency, review TLS inspection, proxy, and endpoint logs for server certificates issued by any of the eight intermediates. Prioritize clients that carry credentials, such as API clients, CI jobs, package managers, and update mechanisms, since an impersonated endpoint receives whatever those clients send.

Longer term

  • Inventory trust stores like dependencies. Track every trust store your software loads, where it comes from, and when it last changed. A root store is a security dependency even when it ships as a data file.
  • Prefer the platform trust store. Libraries and applications that use the operating system's store, or a maintained bundle package, receive fixes through normal updates. Configure HTTP clients to use it wherever the library allows.
  • Flag bundled certificate files in dependencies. A package that ships its own PEM bundle deserves review, and a bundle that has not changed in years is a liability in every future CA incident.
  • Enforce certificate transparency where you can. Clients that require CT proofs would have been protected through this entire exposure window.

How we are tracking affected packages

Packages that bundle trust stores containing the compromised certificates are being tracked two ways. Where a dedicated advisory exists for a package, such as GHSA-6q48-6c7w-9gw2 for certifi on PyPI or CVE-2026-111943 for the Go standard library, it is added to our proprietary vulnerability database and reported in SCA scans like any other advisory.

In parallel, we are searching for packages that bundle affected trust stores without an advisory of their own, and reporting them under CVE-2026-111943 during SCA scans, so the hidden trust stores described above surface in scan results alongside the known components. This work is ongoing as of October 21, 2026, and findings will appear in scan results as each package is confirmed.

Appendix: What Endor Labs customers can do to find Components with affected trust stores

The fix in nearly every case is updating the component that carries the trust store. Endor Labs can help you find those components. Because patches shipped in the last day or two, the practical rule is: anything not on the newly released version should be treated as affected.

The Endor Labs inventory for your scanned applications and containers can be queried using the advanced query feature in the inventory pane (simply paste in the queries below), or by running the endorclt command with the following parameters and substituting in the query specs below:

endorctl api list \
  --resource DependencyMetadata -n <your namespace> \
  --filter "<one of the filters>" --traverse

‍Example filters you can run against your dependency data today:

Container / OS packages (requires container scanning):

Ubuntu:

spec.dependency_data.package_name=="debian://ubuntu/ca-certificates" and spec.dependency_data.resolved_version not in ["20260816ubuntu0.22.04.1", "20260816ubuntu0.24.04.1", "20260816ubuntu0.26.04.1", "20260816ubuntu0.26.10.1"]

Debian:

spec.dependency_data.package_name=="debian://debian/ca-certificates" and spec.dependency_data.resolved_version not in ["20250419~deb12u2", "20250419+deb13u1", "20260816+nmu1"]

Alpine:

spec.dependency_data.package_name=="apk://alpine/ca-certificates" and spec.dependency_data.resolved_version!="20261020-r0"

Chainguard / Wolfi:

spec.dependency_data.package_name=="apk://wolfi/ca-certificates" and spec.dependency_data.resolved_version!="20260909-r3"

‍Application dependencies:

Python certifi:

spec.dependency_data.package_name=="pypi://certifi" and spec.dependency_data.resolved_version!="2026.10.19"

Go standard library:

spec.dependency_data.package_name=="go://stdlib" and spec.dependency_data.resolved_version not in ["go1.27.3", "go1.26.10"]

Rust webpki-roots:

spec.dependency_data.package_name=="cargo://webpki-roots" and spec.dependency_data.resolved_version!="1.0.10"

Electron:

spec.dependency_data.package_name=="npm://electron" and spec.dependency_data.resolved_version not in ["44.9.1", "43.8.1", "42.11.16"]

PHP Composer ca-bundle:

spec.dependency_data.package_name=="packagist://composer/ca-bundle" and spec.dependency_data.resolved_version not in ["1.4.16", "1.6.0"]

We're compiling the full list (including NSS, curl, Node.js, pip, uv, Rustup, and others) and will share it in our follow-up.

‍WHAT ENDOR LABS CAN'T SEE

Some packages vendor their own copy of a CA list rather than relying on the system trust store. We've already confirmed examples in Python and Ruby libraries. These copies may not show up as a versioned dependency and can be hard to find through dependency analysis alone. Endor Labs also has no visibility into trust stores on hosts, network appliances, IoT devices, or third-party SaaS you depend on.

‍RECOMMENDED ACTIONS NOW

1. Update OS trust stores in your base images and hosts to the patched ca-certificates packages, then rebuild and redeploy images.

2. Upgrade language runtimes and libraries that bundle their own trust store (see filters above).

3. If you can't update immediately, explicitly distrust the eMudhra roots as an interim measure.

Ubuntu / Debian:

sudo apt-get update && sudo apt-get install --only-upgrade ca-certificates

Alpine:

apk update && apk upgrade ca-certificates ca-certificates-bundle

Verify the change by confirming no emSign entries remain in your bundle (the result should be 0):

grep -c "emSign" /etc/ssl/certs/ca-certificates.crt

‍