pip 26.2.2 Dropped emSign. Your OS Store Did Not

freenode Oct 20: certifi 2026.10.19, pip 26.2.2, uv 0.13.4. PyPI uploaded 26.2.2 that day. Vendored certifi is not the system bundle.

freenode on October 20: eight eMudhra intermediate certificates shipped with elliptic-curve private keys whose scalar d was less than 256, including at least one key with d equal to 4. Seth Larson reported that the emSign root had to come out of certifi and similar bundles. Floor versions: certifi 2026.10.19, pip 26.2.2, uv 0.13.4, sigstore 4.5.1. That is a CA incident. It is not apt upgrade and done.

PyPI’s pip 26.2.2 page shows the wheel and sdist uploaded October 20, 2026, Trusted Publishing via GitHub Actions from pypa/pip, commit b3b696fe2122f2339d782517559bfade2cb6b879. discuss.python.org is the same incident with a helper script. Three documents. Mix them and you will rebuild a container because Ubuntu distrusted emSign, then watch an old pip inside the image keep trusting it.

We already filed uv 0.12.18’s wheel path and pip-only deps in Docker. This week the pin is 26.2.2, and the reason is a vendored CA file.

d=4 is not a breach narrative. It is a key

freenode’s useful sentence: the failure was cryptographic rather than a conventional CA breach. Weak private keys on eight intermediates. Leaving them trusted exposes TLS clients to man-in-the-middle. Larson: remove the emSign root from certifi. Container images should be rebuilt so they pick up current system certificates. Rebuilding the image is necessary. It is not sufficient if pip inside the image is still 26.2.1.

Christian Heimes, on that same page: pip users must update pip itself. Pip always loads its vendored certifi in addition to the system trust store. There is no supported way to disable that. REQUESTS_CA_BUNDLE and --cert only add authorities. They do not replace certifi. Any pip older than 26.2.2 therefore keeps trusting the compromised intermediates even when the operating system has already distrusted them.

uv users can fall back on the system store with UV_SYSTEM_CERTS enabled. That option does not exist for pip. If your DAG still calls python -m pip install, OS distrust is a press release.

discuss.python.org repeats the floor: users must update pip to >= 26.2.2. truststore is used “in addition to certifi” by design. Same trap, more mailing-list. If a teammate says “we put the system bundle in the env var,” they added a CA. They did not subtract emSign from pip’s copy.

sigstore 4.5.1 is in Larson’s floor list because signing clients ship roots too. If you verify attestations with an old sigstore, you have a second bundle. PyPI 26.2.2’s own page shows attestations signed by GitHub Actions and verified by PyPI on October 20. That is provenance for the new pip. It does not patch the old pip that would fetch it.

The wheel is a checksum, not pip install -U pip

Larson advised obtaining the new wheel through a browser whose certificate store has already been refreshed, verifying the published SHA-256, and only then bootstrapping certifi and other tools. The reason is circular: an old pip is itself the attack target during an upgrade over TLS.

discuss.python.org’s helper script names the file pip-26.2.2-py3-none-any.whl and the hash fd7f4dfa8190e014013b9f08f3e973f4e4c26ffd179a204895533a7254813a70. PyPI’s page for the sdist pip-26.2.2.tar.gz prints SHA-256 3a8e5c800492787f33a4114ef54444546c340df428974ebba17e0fd47e11af2f and BLAKE2b-256 fae80842d757e483e895e670f655084cfe8b8c72580c6769a76b30b2789a0608. Two artifacts. Two hashes. Do not run shasum against the tarball with the wheel hash. The script curls the wheel, checks the wheel hash, then walks $HOME for pyvenv.cfg and runs python -I -m pip --isolated --disable-pip-version-check install --no-index --no-deps --upgrade on that wheel.

The script skips interpreters older than 3.10. If your CI still has 3.9, you are off this helper. You are also off current pip policy in other ways; this incident is not your 3.9 story. Count the venvs. The script’s end line is $count installations found, $failed upgrades failed. Run it twice if you have Docker volumes that are not $HOME.

CPython’s ensurepip bootstrap is being updated in forthcoming interpreter security releases so new virtual environments ship a fixed pip. Until those land, upgrading pip after every venv creation remains the practical workaround. That sentence is how you lose Monday. A fresh python -m venv .venv on 3.15 today can still drop an old pip. We already lived through 3.15rc1 freezing the ABI while Ruff rewrote except. ensurepip is a different freeze. Pin the post-venv upgrade in the same Makefile as the venv.

PyPI 26.2.2 uploaded with twine/6.1.0 CPython/3.13.13, Trusted Publishing yes. 26.2.1 is dated August 4, 2026 on the same history table. If your lockfile says 26.2.1, you are current-minus-incident. That is not a useful kind of current.

uv 0.13.4 is the floor. 1.4x is a different article

Larson’s uv pin is 0.13.4. That is the CA bundle bump. It is not a speed pitch.

Amaresh Pelleti on DEV, October 19, originally at DevToolHub: switching GitHub Actions from pip install -r requirements.txt to uv sync --locked is one official action plus a cache flag. The 10–100x figure from uv’s own docs is a cold install with no cache. Honest CI with a warm cache is closer to 1.4x.

The real numbers he prints are from CPython’s blurb tool, PR python/blurb#32:

JobNo cacheWith cache
Linting (pre-commit)60s → 37s (1.62x)11s → 11s
Testing (tox)2m 0s → 1m 26s (1.40x)1m 58s → 1m 22s (1.44x)

Plan around 1.4–1.6x on a real job. Do not promise the pipeline a 10x because a CA incident made you look at uv.

astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 is the commit he pins as v10.3.0, enable-cache: true. The action downloads uv, puts it on PATH, optionally caches. It does not install Python by default. Pin the SHA. A floating tag on an action with repo access is how you get a different binary than the one you read about.

uv sync --locked fails if uv.lock disagrees with pyproject.toml. That is the flag you want in CI. A surprise bump should break the build. It should not resolve quietly to a different certifi.

tox gotcha, same piece: uv environments do not have pip installed by default. Any tox.ini line that shells out to pip install -r requirements.txt breaks. Fixes he names: deps = -r requirements.txt, tox-uv, uv_seed = True so pip/setuptools/wheel exist. If your incident response is “we moved CI to uv” and your tox still calls pip, you have not moved. You have a red job and an old pip on the laptop.

UV_SYSTEM_CERTS is how uv can ignore its bundle and use the OS. Turn it on after the OS store is actually refreshed. Turning it on against a stale base image is how you keep emSign with extra steps.

26.2.1 is August. 26.2.2 is Tuesday

PyPI’s release table is the boring confirmation. 26.2.2, this release, October 20, 2026, two files, full provenance. 26.2.1, August 4. 26.2, July 29. If your internal mirror’s “latest pip” is still the August wheel, Larson’s floor is not on the mirror. Check the filename, then the hash. Trusted Publishing on the sdist is GitHub Actions, release.yml at that commit, run 39458450372 attempt 1. Attestation is how you know pypa/pip built that tarball. It is not how you know your company’s Artifactory copy is that tarball.

The wheel hash in the discuss script and the tar.gz hash on PyPI are different lengths of the same advice: verify the bytes you actually saved. shasum -a 256 --check against the wrong file fails closed if you let it. People paste the other hash and then --no-verify. Don’t.

Pelleti’s uv run pytest tests after uv sync --locked --all-extras --dev is the CI shape once uv is patched. --all-extras --dev is how test deps get in. It is also how an extra pulls an old certifi if you are not on 0.13.4. Order: uv binary first, lock second, extras third. If you sync extras on 0.13.3, you have a fast installer with a stale bundle.

enable-cache: true without a Python install step is how CI uses the runner’s Python and uv’s installer. The runner’s Python still has ensurepip’s old pip if you mix pip install into a later step “just this once.” Mixed toolchains are how this incident duplicates. Pick pip’s path or uv’s path. Write it in the workflow comment.

What to pin today

If pip in the venv is below 26.2.2, do not pip install -U pip over the network from that venv. Browser, checksum, --no-index --no-deps, then certifi 2026.10.19, then the rest. If uv is below 0.13.4, bump uv first, then decide whether UV_SYSTEM_CERTS is your policy.

If the image was rebuilt Monday because the distro dropped emSign, grep the image for pip 26.2.1 anyway. Distro Python and venv Python are two pip binaries. Docker layers hide the second.

If you create venvs in CI, add the post-create upgrade until ensurepip ships the floor. Write the reason in the comment: vendored certifi, Heimes, no disable switch.

If you were about to migrate tox to uv because of this headline, migrate for the lockfile and the 1.4x, not because Klein-style marketing leaked into packaging. Pelleti’s 11s lint job did not get faster with cache. The CA floor and the CI speedup are adjacent issues on a Tuesday. They are not the same ticket.

Do not steal d=4 as a joke in the standup without also shipping 26.2.2. Do not steal 1.62x as the incident response. Do not steal Trusted Publishing on the 26.2.2 sdist as proof your internal mirror is clean. Mirrors lag. Check the hash on the mirror.

Szárnyas discarded warmup runs last week on DuckDB. Discard the first venv after the bump too. Create it, upgrade pip, freeze, then run the install you actually ship. If the freeze still shows pip 26.2.1, you upgraded the wrong interpreter.

Rebuild the image, then print python -m pip --version and uv --version in the same CI log. If one of them is below the floor, the rebuild did not finish. certifi 2026.10.19 is the library pin for apps that import certifi directly; pip 26.2.2 is the installer pin; uv 0.13.4 is the third pin. Three names. Three lockfiles if you have three tools.

The calendar this week is a CA with d less than 256. The pin is 26.2.2. The OS store is a different file. pip reads both, and the broken one is the one it ships.

Spread The Article

Share this guide

Send this article to your network or keep a copy of the direct link.

X Facebook LinkedIn Reddit Telegram

Discussion

Leave a comment

No comments yet

Be the first to start the conversation.