PyPI blocks new files on releases older than 14 days
PyPI now refuses to upload new files to releases published more than 14 days ago, a preventive measure against poisoning stable releases if tokens leak.
The Python Package Index (PyPI) has closed a door that had been open for years without anyone walking through it. Since 22 July, the repository refuses the upload of new files to any release older than 14 days. Seth Larson, security developer in residence at the Python Software Foundation, explains it in a note that Simon Willison picks up on his weblog.
The detail that makes the change interesting is the reasoning. According to Larson, the restriction exists to prevent old and stable releases from being poisoned if a project's publishing tokens or workflows are compromised. And he admits something unusual in a security announcement: as far as they are aware, this vector has never actually been exploited. There was no technical reason preventing it, beyond attackers not knowing it was possible.
What used to be possible
Until now, nothing stopped you from adding a new file (for example, a wheel for a platform that was not previously covered) to a version published years ago. Under normal conditions it is a convenience. In the hands of an attacker with access to a project's publishing credentials, it is a perfect way to slip malicious code where nobody expects it: inside an old version, marked as stable, that thousands of reproducible deployments pin in their dependency files.
The danger lies precisely in that trust. A release from three years ago that has never changed raises no suspicion. If a new artifact suddenly appears inside it, most automated pipelines would accept it without complaint. Closing that window at 14 days reduces the attack surface without disturbing the legitimate flow, because publishing a version's binaries rarely takes more than two weeks.
A hardening pattern
The change, detailed in warehouse pull request 19727, fits a broader trend at PyPI and other package repositories: gradually closing off legitimate but dangerous behaviours that survive out of inertia. In recent years we have seen mandatory two factor authentication for maintainers, Trusted Publishers based on OpenID Connect to avoid long lived tokens, and now this time window to freeze stable releases.
The logic is always the same: most supply chain attacks do not exploit an exotic flaw, but a normal function used maliciously. Reducing what can be done with compromised credentials is usually worth more than chasing specific vulnerabilities. It is prevention by design, not a reaction to an incident that already happened.
Who it matters to
If you maintain a package on PyPI, the practical impact is minimal: you will only notice the change if you try to add files to a version older than two weeks, something rare in a healthy flow. The implicit recommendation is to publish all of a release's artifacts in its initial window and, if you need a new binary later, to cut a new version instead of touching an old one.
If your job is securing dependency chains, the reading goes deeper. It is worth reviewing where versions are pinned, whether pipelines verify hashes and what would happen if a publishing token leaked today. PyPI's move reduces one specific risk, but it does not replace pinning dependencies by hash or auditing who holds publishing permissions.
At ElephantPink we tend to insist that supply chain security is won with boring decisions like this one, not with flashy tools. Closing a vector that was never exploited, before someone discovers it, is exactly the kind of unglamorous maintenance that holds up an ecosystem millions of projects depend on. We hope the example catches on in other repositories.
Sources
Read next
sqlite-utils 4.1 lets you insert rows with Python code
Version 4.1 of sqlite-utils adds a --code option that lets you generate the rows to insert with a block of Python code, without going through an intermediate file first.
sqlite-utils 4.0rc3: compound foreign keys ahead of the stable release
Simon Willison ships sqlite-utils 4.0rc3 with compound foreign keys and case insensitive column matching, two changes delaying the long awaited 4.0 stable release.
sqlite-utils 4.0rc2: Claude Fable writes most of the release for $149
The creator of Datasette hands Claude Fable the final review of sqlite-utils 4.0: the model caught five release blockers and wrote most of the rc2 for about $149.25.