← Blog
eMudhraCertificate AuthoritySupply ChainTLS

eMudhra CA Keys: Fixing Python, pip, uv and Your Trust Stores

Eight eMudhra CA keys were weak enough to recompute. What's real, what's hype, and how to update certifi, pip, uv and OS trust stores before a MITM.

Zero Hunt Research··9 min read

Published by Zero Hunt, an autonomous AI red team on an on-premise appliance running private AI: automated penetration testing for networks and infrastructure, black-box or gray-box, with a human approving every step that matters.

A viral thread this week framed the eMudhra incident as "Python is facing a massive security emergency." The headline is overcooked; the underlying facts are not. Eight intermediate certificate-authority keys were generated so badly that anyone can recompute the private key from the public certificate — one of them evaluated to the integer 4. Those intermediates chain to roots that Python's certifi, pip, uv, Go, Node.js, curl and most Linux trust stores shipped as trusted. Browsers were patched within hours; the long tail is the tooling developers run all day, and that is the part worth your attention tonight.

Developing story — first published 23:34 CEST (21:34 UTC), 22 October 2026. Updated as vendors ship fixes.

This is not a Python bug, and it is not limited to Python. It is a trust-store problem, and Python is simply one of the clearest places to see it and fix it.

What actually happened

In an April 2025 key-generation ceremony, eMudhra — an Indian CA whose roots are (or were) in the Mozilla, Microsoft, Apple and Chrome programs — produced a weak second set of private keys for eight intermediate CAs alongside the intended strong keys, and it was the weak keys that went into the certificates. On 17 October 2026 a researcher reported signed proof of control over all eight keys to the root programs (eMudhra incident site, maintained by Dennis Jackson). The Next Web confirmed the detail that one private key was simply 4; Christian Heimes of the Python team noted all eight had private scalars below 256.

The reason this matters: an attacker who recomputes one of these keys can mint a valid certificate for any hostname — pypi.org, python.org, your internal registry — and, if they are on the network path, intercept or rewrite TLS traffic to any client that still trusts the chain. That is the monster-in-the-middle scenario the Python Security Response Team wrote up on 20 October.

"I vibe-coded the evil proxy for about $0.30… it reads the SNI, and if the user is asking to talk to PyPI, it intercepts the connection, strips the hashes from the index, and repackages the wheel." — a developer in the docker-library/python thread, 22 October. The attack is cheap to build; the only hard part for the attacker is getting on the wire.

What's true, and what the viral posts exaggerate

Claim in circulation Verdict The actual fact
8 CA keys compromised, one was the number 4 ✅ All eight intermediates had private scalars below 256; one was 4 (eMudhra)
Attackers could impersonate python.org, PyPI and other sites ⚠️ True in principle, but it needs a network-path (MITM) position and a client that skips Certificate Transparency; no misuse has been observed
"Older pip still trusts the bad certs after an OS update" ✅ pip vendors its own certifi and ignores the system store; pip < 26.2.2 keeps trusting them (C. Heimes)
Docker images need rebuilding ✅ Official python:* images shipped pip 26.2.1 until 21 Oct; cached layers keep old bundles (docker-library/python#1142)
Browsers are still exposed ❌ Chrome/Firefox blocked on 17 Oct, Microsoft on 20 Oct, Apple on 22 Oct, via CRLSet/OneCRL (Google)
Certificates were already forged and used ❌ Google found none in CT logs; eMudhra says the production intermediate issued zero certificates (Google, eMudhra statement)

So: real risk, no known exploitation, and the window is narrow because it requires the attacker to be on your network path. That is exactly why a build server on a shared network, a CI runner, or a laptop on café Wi-Fi is the realistic target — not your laptop at home behind one NAT.

Two things keep browsers safe that your tools may not have. The first is Certificate Transparency: Chrome, Firefox and Safari reject a server certificate that was never logged to a public CT log, and none of these intermediates ever logged one. curl, Python, Go and most command-line clients do not enforce CT, which is why they are the soft target. The second is revocation push — CRLSet, OneCRL, CRLite — which command-line tooling also ignores. The fix for everything else is to stop trusting the root.

Remediation

Work in this order: find every client that has its own trust store, update the ones that do, and rebuild anything baked into an image.

1. Am I affected? Check without guessing

Three different trust stores are in play on one machine, and updating one does nothing for the others.

# A) Python's bundled trust store (requests, httpx, pip use this)
python -c "import certifi, sys; print(certifi.__version__, certifi.where())"
# < 2026.10.19  →  vulnerable

# B) pip's OWN vendored copy (separate from the one above)
pip --version              # pip < 26.2.2 ships the old certifi
pip inspect 2>/dev/null | grep -i certifi

# C) uv's bundled webpki roots
uv --version               # uv < 0.13.4 is vulnerable

# D) OS trust store
# Debian/Ubuntu:
dpkg -l ca-certificates | tail -1
# RHEL/Fedora:
rpm -q ca-certificates

The live, authoritative test is the checker on the disclosure site. A connection that succeeds means the client still trusts a compromised intermediate:

# If this returns a page instead of a cert error, that client is still vulnerable
curl https://dv-g3b-emudhra.we-have-a-problem.com/
# A second host tests the Sectigo cross-sign path:
curl https://dv-g3b-aaa-emudhra.we-have-a-problem.com/
# And this tests whether the client also skips Certificate Transparency:
curl https://dv-g3b-noct-emudhra.we-have-a-problem.com/

To grep a specific CA bundle offline for the roots at issue:

grep -E "emSign ECC Root CA - G3|AAA Certificate Services" /path/to/cacert.pem
# emSign ECC Root CA - G3  SHA-256: 86A1ECBA089C4A8D3BBE2734C612BA341D813E043CF9E8A862CD5C57A36BBE6B
# AAA Certificate Services SHA-256: D7A7A0FB5D7E2731D771E9484EBCDEF71D5F0C3E0A2948782BC83EE0EA699EF4

Note the second root: six of the eight intermediates are cross-signed by Sectigo's "AAA Certificate Services", which has been trusted for years and is present in old bundles. Removing only the emSign root is not enough; the AAA cross-sign path has to go too.

2. Patch — exact fixed versions

Verified against each project's release metadata on 22 October 2026:

Tool Fixed version Note
certifi 2026.10.19 2026.10.18 stripped all emSign roots; .19 restored the unaffected ones
pip 26.2.2 vendors certifi 2026.10.19
uv 0.13.4 removes the root from its bundled webpki-roots (uv ignores the OS store by default)
Python (installers) 3.15.1, 3.14.9, 3.13.17 bundle pip 26.2.2; 3.12, 3.11, 3.10 are not getting a pip bump
Node.js 26.12.1, 24.22.1, 22.23.4
Go 1.27.3, 1.26.10 adds an SPKI denylist (CVE-2026-111943)
conda / pixi 26.9.4 / 0.82.1
sigstore 4.5.1

The bootstrap trap: you cannot trust a plain pip install --upgrade pip on a vulnerable machine, because the download itself could be intercepted. The PSRT's instruction is to pin the wheel by hash:

python -m pip install \
  "pip @ https://files.pythonhosted.org/packages/3f/06/3e4420302363fbb71be3c7f35ce042f54bfca330e3e66b3a2e3c4cca579f/pip-26.2.2-py3-none-any.whl#sha256=fd7f4dfa8190e014013b9f08f3e973f4e4c26ffd179a204895533a7254813a70"
# then, with the now-trusted pip:
python -m pip install --upgrade certifi

For uv, bootstrap from the system store that macOS and Windows already fixed: UV_SYSTEM_CERTS=true uv self update. On Linux this works once the distro's ca-certificates is updated.

3. Fix the OS trust store

Update the distro package — this is what Debian's patched pip, curl and most tools read:

# Debian / Ubuntu
apt update && apt install --only-upgrade ca-certificates
# RHEL / Fedora / Alma / Rocky / Amazon Linux 2023
dnf upgrade ca-certificates
# Alpine
apk upgrade ca-certificates
# macOS: the OS blocks them, but built-in curl/git/python3 read /etc/ssl/cert.pem,
# which is NOT auto-updated — see the disclosure site for the awk one-liner that strips the two roots.

Patched ca-certificates versions include Debian 20250419+deb13u1, Ubuntu 20260816ubuntu0.24.04.1, Alpine 20261020-r0, Fedora 2026.2.92_v9.0.319 (disclosure site). If your distro has not shipped one yet, the same site lists the exact update-ca-certificates / trust extract blocklist commands per distro — use those as the stopgap, not a permanent config.

4. Rebuild containers and clear caches

A patched host does nothing for an image built last week.

# Rebuild WITHOUT layer cache so the new ca-certificates/pip/certifi are fetched
docker build --no-cache -t myapp:fixed .
  • Pull fresh base images: official python:* images were updated after 21 October (3.13–3.15 track CPython; 3.11/3.12 images keep the old pip — add PIP_CERT=/etc/ssl/certs/ca-certificates.crt or pin pip 26.2.2 by hash in the Dockerfile).
  • Invalidate CI dependency caches (GitHub Actions actions/cache, GitLab cache, Docker layer cache) that hold a stale certifi wheel or cacert.pem.
  • Hunt for vendored copies that no package manager will touch:
find / -name "cacert.pem" 2>/dev/null         # bundled bundles in app dirs
find / -path "*certifi/cacert.pem" 2>/dev/null

Each one that still contains emSign ECC Root CA - G3 needs its owning package updated or the file replaced.

5. Verify and prevent

Re-run the curl checker against every client class you fixed; a certificate error now means you are clean. Then harden against the next bad CA:

  • Pin with hashes. pip install --require-hashes and lockfiles (uv, Poetry, pip-tools) turn a swapped wheel into a hard failure, which is the real defense when the transport is in doubt.
  • Prefer the OS trust store where you can (truststore for Python, UV_SYSTEM_CERTS for uv), so one ca-certificates update covers the tool instead of N vendored bundles.
  • Restrict CI egress and use a private, hash-verified mirror so a build runner cannot be redirected to an attacker's index.
  • Rebuild images on a cadence, not only when something breaks — a monthly base-image refresh would have closed this in days without a scramble.

What is not known yet

eMudhra states it found no evidence the weak keys were recovered or used before disclosure, and that the one production intermediate issued zero certificates (eMudhra statement). Because CT-skipping clients leave no public record, the honest answer for non-browser tooling is that absence of evidence is not proof no one used a forged cert against a curl/pip client on a hostile network — which is the whole reason to update now rather than wait for the 27 October Chrome root removal. CPython's ssl module denylist for the intermediates (issue #160042) was still in review at the time of writing; the per-tool fixes above do not depend on it.

Where Zero Hunt fits

The gap this incident exposes is not "is there a patch" — there is — but "which of my hosts still trusts a CA it shouldn't, and could an attacker on my network actually use that." That is a validation question, and it is the one Zero Hunt answers. As an autonomous AI red team on an on-premise appliance, it enumerates the TLS clients and trust stores on a segment and, at an operator-approved autonomy level, demonstrates whether a host on the LAN can be positioned for interception — the on-path step this attack needs — then re-tests after you update the bundles to confirm the client now rejects the forged chain. Every action runs with a human in the loop and nothing leaves the perimeter, which matters when the thing you are testing is your own certificate trust. It does not replace patching certifi and ca-certificates; it tells you, with proof, whether the patching is actually complete across the machines you forgot you had.

For the related supply-chain story from the same week, see our note on the tampered LiteLLM 1.107.0 PyPI release and the Black October incident tracker. For how to triage when every advisory says "critical," see CISA's BOD 26-04 risk-based tiers.

Is this exploitable in your environment?

Zero Hunt answers that on your own network: an autonomous AI red team on an on-premise appliance, running on private AI, black-box or gray-box, with a human approving every step that matters. Proof of what is exploitable, the fix, and signed evidence — no data leaves your perimeter.