# PyPI Now Rejects New Files on Releases Older Than 14 Days


Pinning `requests==2.32.0` in your [lockfile](https://pydevtools.com/handbook/explanation/what-is-a-lock-file.md) 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](https://pydevtools.com/handbook/explanation/what-is-pypi.md) closed this path on July 8. Releases older than 14 days now reject new file uploads. [Warehouse PR #19727](https://github.com/pypi/warehouse/pull/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](https://pydevtools.com/blog/litellm-supply-chain-attack-and-securing-python-dependencies.md) and [lightning attack](https://pydevtools.com/blog/lightning-pypi-compromise-import-time-supply-chain-attack.md) 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](https://pydevtools.com/handbook/how-to/how-to-publish-to-pypi-with-trusted-publishing.md) 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](https://peps.python.org/pep-0694/), 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](https://pydevtools.com/blog/uv-malware-check-what-it-blocks-and-what-it-misses.md) addresses known-malware advisories. The [dependency cooldown](https://pydevtools.com/handbook/explanation/what-is-a-dependency-cooldown.md) 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

- [PyPI announcement: Releases now reject new files after 14 days](https://blog.pypi.org/posts/2026-07-22-releases-now-reject-new-files-after-14-days/)
- [PEP 694: Upload 2.0 API for Python Package Repositories](https://peps.python.org/pep-0694/)
