Back to Blog

eMudhra / emSign CA Compromise: What Python, pip, and CI/CD Users Need to Update

Compromised eMudhra intermediate CA keys put Python dependency downloads at risk. Here are the affected tools, verified certificate-trust results, and remediation steps for pip, uv, virtual environments, and CI/CD images.
Venkata Gopi Yamparala
View LinkedIn

October 22, 2026

Share on X
Share on X
Share on LinkedIn
Share on Facebook
Follow our RSS feed
eMudhra / emSign CA compromise: update certifi, pip, and uv in every environment and CI/CD image
Table of Contents

Overview

On October 20, 2026, Python’s security team advised users to update Python packaging tools following the eMudhra / emSign certificate authority incident. The immediate concern is the certificate trust used when those tools connect to package indexes and other HTTPS services. Python security announcement

Google confirmed that eight intermediate CA certificates had private keys generated with insufficient randomness. Anyone who recovers such a key can create certificates that impersonate websites to a client that still accepts the affected chain. An attacker must also intercept or redirect the victim’s connection to exploit that trust. Google’s incident report

For software delivery, the risk is straightforward: a dependency downloader could accept an attacker’s HTTPS response as authentic. This attack path does not require compromising the package registry itself. The disclosure’s authors say it remains unknown whether attackers exploited the weakness against non-browser clients. Coordinated disclosure

Action Required

Start with these upstream remediation versions, identified as of October 22, 2026. Check custom certificate bundles and distributor backports alongside the version numbers.

ComponentRecommended updateWhat to check
certifi2026.10.19 or laterUpdate each application environment that uses its CA bundle.
pip26.2.2 or laterUpdate pip itself in each environment; it bundles its own certifi.
uv0.13.4 or laterUpdate every installed binary, or verify an explicitly selected system or custom trust store.
sigstore Python client4.5.1 or laterInclude signing and verification environments in the update inventory.
conda26.9.4 or laterUpdate CA packages in other environments as well as base.
Pixi0.82.1 or laterUpdate through the installation method used for each copy.

Sources: Python’s update list, pip changelog, uv advisory, and conda ecosystem guidance.

The certifi advisory identifies 2026.10.18 as the initial fix. Python recommends 2026.10.19 because it restores unaffected emSign roots while retaining the security change. Use 2026.10.19 or later for the upgrade target. certifi advisory, Python guidance

How the Certificate Trust Failed

A certificate authority signs certificates that clients use to authenticate servers. Intermediate CAs sit between a trusted root and the server certificate. Possession of an intermediate CA’s private key can therefore let an attacker issue certificates for services they do not operate.

Mozilla’s incident record identifies six affected TLS intermediates and two code-signing intermediates. It also records cross-signed versions, where the same keys can be reached through another certificate chain. That is why remediation must account for alternate chains, not just one certificate filename. Mozilla incident record

Browser vendors distributed blocks through their own revocation mechanisms. Google, for example, blocked the affected intermediate public keys through Chrome’s CRLSet. Developer tools do not necessarily receive or enforce those browser updates. A browser that rejects an affected chain says nothing conclusive about the Python process running beside it. Google’s response

Why Updating certifi Alone Does Not Fix pip

There can be several independent copies of certificate trust on one machine: the OS store, an application’s certifi installation, pip’s vendored certifi, and a tool’s embedded or explicitly configured bundle.

Upstream pip uses system certificates in addition to certifi by default in its current configuration. Updating the OS therefore does not necessarily remove trust supplied by pip’s bundled certificates. The 26.2.2 release specifically upgrades that bundled certifi to 2026.10.19. Updating the separate certifi package in a project does not replace pip’s vendored copy. pip certificate documentation, pip release notes

uv has a different configuration model. Its advisory says users of the default bundled roots need the update; users selecting system or custom certificates must verify that the selected store rejects the affected chains. uv advisory

Distribution packages may patch these defaults or backport fixes. Use the distributor’s security guidance for OS-managed Python installations, and inspect the actual environment rather than judging it solely by the upstream version number.

What We Verified

We tested the fourteen certificate chains published by the disclosure authors against three isolated CA bundles on October 22, 2026. The comparison used OpenSSL 4.0.3, with hostname and TLS-server-purpose checks enabled and default CA directory and store lookups disabled. It did not enable Certificate Transparency or online revocation checks.

CA bundle testedChains acceptedChains rejected
certifi 2026.07.227 of 147 of 14
certifi 2026.10.190 of 1414 of 14
certifi bundled inside pip 26.2.20 of 1414 of 14

The older bundle accepted all seven supplied paths through the emSign root, including the test certificate without a Certificate Transparency entry. It already rejected the seven AAA Certificate Services paths. Both corrected bundles rejected all fourteen.

Before and After the CA Bundle Update

For the same dv-g3b-emudhra.we-have-a-problem.com test chain, the older bundle produced this output:

dv-g3b-emudhra.we-have-a-problem.com.pem: OK
Chain:
depth=0: CN=dv-g3b-emudhra.we-have-a-problem.com (untrusted)
depth=1: C=NL, O=eMudhra BV, CN=EM DV TLS CA - G3B (untrusted)
depth=2: C=IN, O=eMudhra Technologies Limited, CN=emSign Root TLS CA - G3 (untrusted)
depth=3: C=IN, OU=emSign PKI, O=eMudhra Technologies Limited, CN=emSign ECC Root CA - G3

With certifi 2026.10.19, verification failed with exit code 2:

C=IN, O=eMudhra Technologies Limited, CN=emSign Root TLS CA - G3
error 20 at 2 depth lookup: unable to get local issuer certificate
error dv-g3b-emudhra.we-have-a-problem.com.pem: verification failed

The pip 26.2.2 wheel's vendored certificate bundle was byte-for-byte identical to certifi 2026.10.19 and produced the same rejection. We also verified the wheel against the SHA-256 digest in Python's security announcement before inspecting it.

These are local certificate-verification results, not attack telemetry. They demonstrate the trust change in the tested bundles; an application's system or custom trust configuration still needs its own check. The disclosure site supplies the certificate chains and verification instructions.

Am I Affected?

Run these checks with the interpreter used by the application or build job:

python --version
python -m pip --version
python -m pip show certifi
python -c "import ensurepip; print(ensurepip.version())"

The last command reports the pip version bundled with that interpreter. Some distributors omit ensurepip. A missing standalone certifi package also does not establish that pip has no bundled CA store. ensurepip documentation, pip vendoring release notes

Build the inventory around execution environments: developer virtual environments, self-hosted runner images, job containers, build stages, and application containers making outbound HTTPS requests. Check tool copies installed through separate mechanisms, such as a standalone uv binary and one installed in a Python environment.

Inspect explicit trust configuration too, including PIP_CERT, REQUESTS_CA_BUNDLE, SSL_CERT_FILE, SSL_CERT_DIR, and uv’s system-certificate setting. Upgrading a package cannot repair an old custom bundle that the application continues to select. pip configuration, uv certificate configuration

Remediation Steps

Bootstrap a corrected pip securely

For an upstream pip installation on Python 3.10 or newer, Python’s security team provides this direct wheel URL with an expected SHA-256 digest. The digest binds the installation to the published wheel even when the existing pip’s TLS trust is suspect. Obtain the command through a trusted, updated browser or another trusted channel. Python’s secure update instructions

python -m pip install "pip @ https://files.pythonhosted.org/packages/3f/06/3e4420302363fbb71be3c7f35ce042f54bfca330e3e66b3a2e3c4cca579f/pip-26.2.2-py3-none-any.whl#sha256=fd7f4dfa8190e014013b9f08f3e973f4e4c26ffd179a204895533a7254813a70"
python -m pip install --upgrade "certifi>=2026.10.19"
python -m pip --version
python -m pip show certifi

Repeat for each affected virtual environment. For OS-managed installations, apply the distributor’s fix through its package manager.

pip 26.2.2 requires Python 3.10 or later. An older interpreter that cannot install this release still needs remediation through a supported Python upgrade or an applicable distributor fix. pip release metadata

Update uv through a corrected trust source

For uv installed using its standalone installer, the following uses the operating system verifier during self-update:

UV_SYSTEM_CERTS=true uv self update
uv --version

Use this only after confirming that the system verifier rejects the compromised chains. For a package-manager installation, update through that package manager. Custom CA files require their own review. uv certificate configuration, uv upgrade instructions, Python discussion of uv bootstrapping

Update the Python installation that creates new environments

Upgrading pip inside one virtual environment does not update the wheel stored in the parent interpreter’s ensurepip module. That older interpreter can seed another environment with the old pip. Running ensurepip --upgrade only uses the bundled wheel; it does not download a current release. ensurepip documentation

We reproduced this distinction in a disposable Python 3.14.7 environment: after installing the verified pip 26.2.2 wheel, pip reported its updated certifi as 2026.10.19, while ensurepip.version() still returned 26.2.1. That seed belongs to the interpreter, not the upgraded virtual environment.

On October 21, Python released 3.15.1, 3.14.9, and 3.13.17, each bundling pip 26.2.2 and its updated certifi. Update the appropriate interpreter distribution and handle existing virtual environments separately. Until that is complete, make the corrected pip bootstrap an explicit step before new environments download application dependencies. Python emergency release announcement

Rebuild containers and refresh CI images

Update the Dockerfile’s Python tooling and OS CA packages, along with any dependency pins that would restore older versions. Select a corrected base image; if it is pinned by digest, change the digest explicitly.

Then rebuild:

docker build --pull --no-cache -t example-app:ca-update .

Docker documents two separate effects: --pull checks for an updated base image, while --no-cache reruns build layers. Neither overrides an old version pin or repairs a Dockerfile that never updates the affected packages. Check both build and runtime stages, verify the resulting image, and replace deployed containers and runner images. Docker build guidance

Refresh cached virtual environments and tool installations that would reintroduce the old versions. Record the interpreter, package-manager versions, and rebuilt image digest in the remediation change.

Verify certificate rejection

The disclosure site provides affected-chain test endpoints and an offline chain bundle. Test with the actual client and trust configuration used in the workload, including cross-signed paths. A browser check or a curl check does not establish what pip or uv will accept. A timeout or DNS failure also does not prove certificate rejection. Verification resources

Keep TLS verification enabled. If a legitimate internal package index stops working after trust removal, replace its affected certificate chain. Bypassing verification would reopen the authentication problem the update is intended to fix. Google’s certificate migration guidance

Protect Dependency Downloads with Trusted Hashes

For pip workflows, maintain reviewed requirements with pinned versions and independently trusted SHA-256 hashes for every dependency. Enforce them during installation:

python -m pip install --require-hashes -r requirements.txt

This command requires a requirements file that already contains the necessary hashes. Hashes learned from the same intercepted download do not provide an independent integrity check. Version pinning alone does not authenticate the downloaded bytes. pip secure-install guidance

When assessing exposure, distinguish an outdated trust store from evidence of an intercepted download. Investigate suspicious artifacts, unexpected process execution, and credential use if there are signs of exploitation. The existence of an affected CA bundle alone does not establish that an environment was compromised.

How StepSecurity Helps Secure CI/CD

Harden-Runner gives security teams visibility into outbound connections and correlates activity with CI workflow steps. Its network policies can restrict destinations, and its monitoring helps teams investigate unexpected behavior during dependency installation and builds. Harden-Runner documentation

Those controls complement corrected TLS trust and independently verified artifacts. A hostname allowlist alone cannot authenticate an HTTPS response when the client accepts a forged certificate for an allowed hostname. For this incident, updating the package managers and every trust store they use remains essential.

Start free with StepSecurity to monitor dependency-installation activity and investigate unexpected outbound connections in your CI workflows.

Secure the workflows that download your dependencies

Harden-Runner monitors outbound connections during dependency installation and builds, and its network policies restrict where your CI workflows can connect.

References

Explore Related Posts

‍