
eMudhra / emSign CA Compromise: What Python, pip, and CI/CD Users Need to Update
Guidance on the October 2026 eMudhra / emSign CA incident, where eight intermediate CA certificates had private keys generated with insufficient randomness. It explains why updating certifi alone does not fix pip's vendored bundle, and lists fixed versions (certifi 2026.10.19, pip 26.2.2, Python 3.15.1, 3.14.9, 3.13.17). StepSecurity's OpenSSL 4.0.3 tests showed the older bundle accepted seven emSign paths, while both corrected bundles rejected all fourteen.
What it is
On 20 October 2026, Python's security team advised updating Python packaging tools after the eMudhra / emSign CA incident. Google confirmed that eight intermediate CA certificates (six TLS, two code-signing, per Mozilla's incident record) had private keys generated with insufficient randomness. Anyone who recovers such a key can issue certificates for sites they do not run. They also need to intercept or redirect the victim's connection. The disclosure's authors say it is unknown whether non-browser clients were attacked. Package registry compromise is not required.
Preconditions / Affected
Browsers got blocks through mechanisms such as Chrome's CRLSet. Developer tools do not receive those, so a browser rejecting a chain says nothing about pip or uv on the same machine.
A machine can hold several independent trust copies: the OS store, an application's certifi, pip's vendored certifi, and any embedded or configured bundle. Points specific to this page:
- Upstream pip trusts system certificates in addition to certifi, so an OS update does not remove trust from pip's bundled copy.
- Updating the standalone certifi package does not touch pip's vendored copy. A missing standalone certifi does not show pip has no bundled store.
- uv: users of the default bundled roots need the update. Users who select system or custom certificates must check that the chosen store rejects the affected chains.
- Cross-signed chains reach the same keys, so checks must cover alternate paths.
- pip 26.2.2 needs Python 3.10 or later. Older interpreters need a supported Python upgrade or a distributor fix.
- Distributions may backport fixes, so upstream version numbers alone can mislead.
What to do
Target versions as of 22 October 2026: certifi 2026.10.19 or later (2026.10.18 was the first fix, but 2026.10.19 restores unaffected emSign roots), pip 26.2.2, and Python 3.15.1, 3.14.9 or 3.13.17 (released 21 October, each bundling pip 26.2.2).
- Bootstrap pip safely. Python's announcement gives a direct wheel URL with a SHA-256 digest, so the install does not depend on the old pip's trust. Get that command from a trusted channel. Repeat for each virtual environment. For OS-managed Python, use the distributor's package manager.
- Fix the seed interpreter. Upgrading pip in a venv does not change the wheel in the parent interpreter's ensurepip.
ensurepip --upgradeonly uses that bundled wheel. The authors reproduced this on Python 3.14.7: after installing the verified wheel, pip reported certifi 2026.10.19 whileensurepip.version()still returned 26.2.1. Until the interpreter is updated, make the verified pip bootstrap an explicit step before new environments fetch dependencies. - uv. The standalone installer's self-update can use the OS verifier, but only after confirming that verifier rejects the bad chains. Package-manager installs update through that manager. Check separate uv copies, such as a standalone binary and one inside a Python environment.
- Audit explicit trust settings:
PIP_CERT,REQUESTS_CA_BUNDLE,SSL_CERT_FILE,SSL_CERT_DIR, and uv's system-certificate setting. A package upgrade cannot fix an old custom bundle that is still selected. - Containers and CI. Update Python tooling and OS CA packages in the Dockerfile and remove old version pins. A base image pinned by digest needs the digest changed explicitly.
--pullonly checks for a newer base image, and--no-cacheonly reruns layers. Neither overrides an old pin. Check build and runtime stages, replace deployed containers and runner images, refresh cached venvs, and record interpreter, tool versions and image digest. - Verify with the real client. The disclosure site offers test endpoints and an offline chain bundle. A curl or browser result does not predict pip or uv. A timeout or DNS failure is not a rejection.
- Pin hashes. Install from reviewed requirements with pinned versions and independently obtained SHA-256 hashes for every dependency. Hashes taken from the same intercepted download add nothing.
The page's command blocks were not in the fetched text, so the exact commands must be taken from the source.
Caveats
StepSecurity's tests used OpenSSL 4.0.3 on 22 October 2026, with hostname and server-purpose checks on, default CA lookups off, and no Certificate Transparency or online revocation checks. The older bundle accepted all seven emSign paths and already rejected the seven AAA Certificate Services paths. Certifi 2026.10.19 and the pip 26.2.2 vendored bundle (byte-identical) rejected all fourteen. These are local verification results, not attack telemetry. An outdated bundle alone is not evidence of compromise. Bypassing TLS verification for an internal index reopens the problem, so replace that index's affected chain instead. Harden-Runner egress allowlists cannot stop a forged certificate for an allowed hostname.
Organisations named here
- certificate authority compromise
- CRLSet
- emSign
- eMudhra
- intermediate CA
Technologies named here
- Python security announcement
- certifi
- Python interpreters
- Disclosure site by the disclosure authors
- Chrome CRLSet
- Mozilla incident record
- Docker build
- StepSecurity Harden-Runner
- ensurepip
- pip
- Python
- uv
- Docker
- Harden-Runner
- StepSecurity
Techniques named here
- Inventorying trust stores per execution environment
Related in Supply Chain


