uv 0.12.18 Fixed a Wheel Path. pip Did Not Grow --check

Nesbitt’s September 26 package-management week: GHSA-2cv4-cqwr-gwf7 on Windows wheels, plus --check and JSON for uv pip install. Poetry 2.5 stopped calling pip to uninstall.

Andrew Nesbitt’s This Week in Package Management for 26 September 2026 is a changelog, not a conversion sermon. uv 0.12.18 fixes GHSA-2cv4-cqwr-gwf7, a path traversal during wheel installation on Windows. The same release adds --check and --output-format json to uv pip install and uv pip sync, so you can print the plan and leave the environment alone.

That is CI language. A dry run that speaks JSON is how a job fails closed before it writes site-packages. pip did not grow that pair of flags in Nesbitt’s week. If your automation still runs pip install -r and hopes, this is the week’s actual Python story.

We already did the uv project-management tour and the ruff/uv/ty toolchain. This is not another on-ramp. This is a CVE plus a flag you can put in GitHub Actions.

The Windows wheel path is the incident

GHSA-2cv4-cqwr-gwf7 is path traversal during wheel installation on Windows. Nesbitt’s sentence is short. Treat it as short. Do not invent a root-cause essay the advisory summary did not give you. If you install wheels on Windows runners, or you ship a tool that does, 0.12.18 is the floor.

Unix CI does not make this theoretical. Developers install on laptops. Release jobs sometimes still use windows-latest. A traversal during wheel install is a supply-chain bug in the installer, which is a worse neighborhood than a bad package you chose. You chose uv, or uv-as-pip, to unpack bytes. The unpacker is in the blast radius.

Pin the uv version in CI the way you already wish you pinned Playwright’s wheel. astral-sh/setup-uv with a floating major is how last week’s installer becomes this week’s CVE with no diff in your repo. Put version: 0.12.18 or later in the action, or install a hashed binary. Then run the job twice.

If you cannot move off pip this month, you still need to read the advisory for anything else that unpacks wheels on Windows. Nesbitt listed uv’s fix, not pip’s. Absence is not a pip all-clear. It is a uv changelog.

Do not “wait for the next LTS of the installer.” uv does not work like that. The number is 0.12.18. Upgrade.

--check is the automation feature

uv pip install --check and uv pip sync --check report planned changes and do not apply them. --output-format json makes that report a machine object. That is the difference between a log you grep and a step you can jq.

A useful job is boring. Generate the plan on the pull request. Fail if the plan would uninstall something you did not expect, or if it would pull a new hash. Apply on main, or apply in a separate job that needs the plan job. You already know this shape from Terraform. uv just grew a tiny version of it for site-packages.

uv pip sync is the stricter cousin. Sync means the environment should match the file. Check means you get to see the delta. If your runner is a snowflake because a previous job pip-installed a debug extra, --check is how you notice before tests pass on the wrong graph.

JSON output is for humans who are tired. A markdown comment that dumps the plan is optional. A failed step that prints would install is enough. Do not build a bot that lectures the author about uv ideology. Print the packages.

If your image cannot run uv yet, the Medium on-ramp from September 24 still has the boring commands: uv venv, uv pip install -r requirements.txt, uv pip compile requirements.in --universal -o requirements.txt. That article is a migration pamphlet. Nesbitt is the week’s CVE. Use the pamphlet only if you need a command spelling. Do not cite Medium for GHSA IDs.

--check does not replace hashes. A plan can be perfect and still install a yanked wheel if your input file is a name and a float. Pin. Then check. Then sync.

Poetry 2.5 stopped phoning pip to uninstall

Same Nesbitt issue: Poetry 2.5.0 adds installer.builtin-uninstall, which removes packages itself instead of calling pip. It supports Python 3.15. It stops sending credentials that were configured for an HTTPS repository over plain HTTP. 2.5.1 fixes a TypeError in the new uninstaller.

Three different bugs in one paragraph. The uninstall one is about control. If Poetry shells out to pip to delete, your story about “Poetry is the installer” was always a white lie. Builtin uninstall is the lie getting smaller.

Python 3.15 support is a calendar item we already tracked on the 3.15rc2 wheel path. Poetry catching up is not the interpreter release. It is the lock tool admitting the next interpreter exists. If your poetry.toml still pretends 3.14 is the horizon, 2.5.0 is the nudge. If you are not on Poetry, skip this bullet.

The HTTPS-over-HTTP credential fix is the one that should scare you more than 3.15. A config that says the repo is secure and a fallback that posts the password in clear text is an automation classic. Upgrade Poetry before you debate 3.15 typing. 2.5.1 is the TypeError patch on the new uninstaller. Take both.

uv and Poetry in one week does not mean you should run both. It means installers are where CVEs and dry-runs live now. Pick one graph. Pin it. Read its changelog on Fridays.

The rest of Nesbitt’s week is not your pytest job

Flatpak 1.18.3 updated vendored bubblewrap to 0.12.0 for CVE-2026-87766, sandbox setup following a parent symlink through /oldroot onto the host, and xdg-dbus-proxy 0.1.8 for CVE-2026-93676, D-Bus broadcast filters ignoring path, interface, and member restrictions. That is Linux desktop packaging. It is here to remind you that “installer bugs” are a genre. It is not a pip flag.

npm 12.1.0 staged-publishing tokens, RubyGems cooldown flags, Terraform policy GA, winget source priority, mise wanting GitHub attestations: Nesbitt’s page is a mall. Steal the uv and Poetry aisles. Walk past the rest unless you also ship those tools.

The Medium piece’s Option B, uv init --bare then uv add -r requirements.txt then uv sync, is how a requirements project becomes a lockfile project. Fine for a greenfield. Dangerous as a Friday rewrite of a Django app that already has a working pip-tools file. Option A keeps the pip commands and prefixes uv. A commenter in that article stays on classic pip forever and lets uv own venvs. That is a valid cowardice. Cowardice is how you ship.

uv python install and uvx are still the features pip will not grow. They are not this CVE. Do not hide a Windows path traversal behind a pyenv replacement story.

Nesbitt also notes uv pip install and uv pip sync both got --check and JSON. People who only wrap install will miss the sync case. Sync is the one that deletes extras. A PR that adds a debug wheel in a previous job should fail --check on sync, not pass because install would only add. Put the flag on the command you actually use to converge the runner.

Poetry’s HTTP credential fix is the other automation landmine. If a job has POETRY_HTTP_BASIC_* or a repo URL with an embedded token, and something in the resolver retries http://, 2.5.0 is the version that refuses to send the HTTPS-configured secret on the clear channel. Rotate the token anyway if logs ever printed a URL. Then upgrade.

mise 2026.9.14 letting a registry require a verified GitHub attestation is the same week’s “prove the binary” energy. It is not Python. It is the shape: installers are growing proofs. uv’s proof this week is a GHSA number and a dry-run flag. Do not wait for attestations on every wheel before you pin 0.12.18.

What to change in the workflow file

Bump uv to 0.12.18 or newer on every runner OS you actually use, including Windows.

Add a job, or a step, that runs uv pip sync --check --output-format json against the lock or requirements you already trust. Fail on unexpected plans. Keep the apply step separate.

If you are on Poetry, 2.5.1 minimum this week. Confirm installer.builtin-uninstall is the path you want. Confirm you are not still putting HTTPS credentials in a URL that can downgrade.

Do not add a weekly model of package-manager identity. Add a pin and a dry run. Nesbitt’s uv blurb is two sentences. Your YAML should be about that long. Friday changelog reading is the whole culture here: one GHSA, one flag, one Poetry patch, then stop.

If --check tells you the environment would change and you did not mean it, that is a successful test. If you never run --check, you will learn from production, which is the old pip lifestyle, and production is a rude teacher.

The title is mean to pip because pip did not ship this pair of flags in the week’s notes. pip remains the compatibility surface uv pretends to be. Use the surface. Pin the binary that unpacks the wheel. On Windows, that binary needs the GHSA fix. That is the automation news. The rest is a mall.

A worked check step looks like this in spirit, not as a paste-me template you never edit: checkout, install a pinned uv 0.12.18, uv pip sync --check --output-format json against the lockfile the repo already committed, then a separate job that syncs for real and runs pytest. If the JSON says the environment would drift, the PR is yellow before tests burn minutes. That is the same discipline as failing a Playwright pin, just applied to the installer.

If Windows is not in your matrix, still bump uv. Developers unpack wheels. The GHSA is named for that OS. The --check flag is for every OS. You can have one without the other. This week shipped both. Take both, then leave the mall.

RubyGems cooldown flags and npm staged tokens in the same Nesbitt post are other ecosystems learning to slow down publishes. Python’s version of slow is a dry-run that does not write. You already have the lockfile. You did not have a first-class --check on uv pip sync until 0.12.18. That is enough news for a Friday pin. Read the GHSA page, then bump the action, then go home. Do not rewrite the whole toolchain on a Sunday night either, thanks.

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.