Skip to content

PyPI Now Rejects New Files on Releases Older Than 14 Days

July 23, 2026·Tim Hopper · Markdown

Pinning requests==2.32.0 in your lockfile was supposed to keep you safe. If an attacker uploaded requests==2.32.1 with a backdoor, your locked environment wouldn’t touch it. But there was another path: a compromised maintainer token could upload a new wheel to the 2.32.0 release itself (a Python 3.14-compatible build, say) without creating a new version number. Any user without a hash-verified lockfile, or who regenerated their lockfile after the upload, would pull the poisoned wheel under the same version number they already trusted.

PyPI closed this path on July 8. Releases older than 14 days now reject new file uploads. Warehouse PR #19727 implements the check server-side; no client changes are needed.

Blocking the mutable-release attack

A release on PyPI is a collection of files: typically one source distribution and one or more wheels targeting different Python versions and platforms. Before this change, a maintainer with a valid upload token could add new files to any existing release indefinitely. A stolen or compromised token could slot a malicious wheel into a stable, widely-trusted release that users have pinned for months.

This attack is harder to detect than a new malicious version. The LiteLLM compromise and lightning attack both involved new version numbers, which at least required a lockfile update to exploit. Poisoning an existing release required nothing from the victim.

Bump the version to add new wheels

PyPI’s analysis found 56 of the top 15,000 packages had published Python 3.14-compatible (cp314) wheels to releases older than 14 days — the most common legitimate reason for post-release file additions.

If you’ve been doing that, you now need to publish a new patch version instead. A package at 2.3.0 that needs a Python 3.14 wheel becomes 2.3.1. The policy received consensus at PyCon US 2026’s Packaging Summit, where attendees agreed it was reasonable to require a version bump for new Python compatibility.

The trusted publishing workflow is unchanged. The restriction applies at the file level regardless of authentication method.

Watch for PEP 694’s formal release states

PyPI describes the current behavior as a temporary measure with no formal semantics: “Users should not yet rely on this behavior as there are no defined semantics for ‘releases no longer accepting new files.’” PEP 694, which proposes an Upload 2.0 API for Python package repositories, defines upload session states and may eventually provide a more formal mechanism for managing release finality, but does not currently specify explicit publisher-controlled “closed” release states.

The uv malware check addresses known-malware advisories. The dependency cooldown can partially overlap: uv’s exclude-newer skips any distribution uploaded within the cooldown window, which catches a newly poisoned file on an old release if the upload is recent enough. This server-side policy closes the gap regardless of upload recency, by blocking the addition at PyPI before any client ever sees it.

Learn More

Last updated on