Most developers assume that once a software package is published, it becomes a static piece of history. You lock your dependencies, specify a version like requests==2.28.1, and assume you will get the exact same code every time you build your container or deploy to production. But package registries do not always work like immutable ledgers.
On the Python Package Index, commonly known as PyPI, a release is not a single file. It is a collection of files. When a maintainer publishes a new version of a library, they might upload a source distribution first. Later, they might upload precompiled binary wheels for macOS and various Linux architectures. Historically, there was no time limit on this. A maintainer, or anyone who gained access to their account, could upload a new wheel to a version that was released five years ago.
This open window created a significant security gap. PyPI recently closed it by introducing a strict 14-day limit on uploading new files to existing releases. It is a simple change, but it fundamentally alters how python supply chain security works.
The Architecture of a Python Release
To understand why this change matters, we have to look at how python packages are distributed. When you build a library, you typically generate two types of files: source distributions and wheels.
A source distribution, or sdist, contains the raw source code. If your library includes C extensions, the user's machine has to compile them during installation. This requires compiler tools on the target system, which can make installations slow and error-prone.
A wheel is a precompiled binary format. It is built for a specific Python version and CPU architecture. When you run pip install, pip checks if there is a compatible wheel for your system. If it finds one, it downloads it and unpacks it directly. This bypasses the compilation step entirely.
Because wheels offer a faster install experience, pip always prefers a compatible wheel over a source distribution. If a release has both package-1.0.0.tar.gz and package-1.0.0-cp310-cp310-manylinux.whl, pip will pull the wheel every time you install on a compatible Linux system.
This prioritization logic is highly efficient, but it also creates a subtle vulnerability.
The Mechanics of Post-Release Poisoning
Imagine a popular library that was last updated in 2021. The maintainer uploaded a source distribution, but they never got around to building wheels for every single platform. The release works fine, and thousands of projects depend on it, pinning it to version 1.0.0.
Now, fast forward to today. An attacker wants to compromise systems running this package. They do not want to publish a new version like 1.0.1 because that triggers alerts. Security tools monitor package registries for new versions, and developers might notice their dependency scanners flagging an unexpected update.
Instead, the attacker targets the old 1.0.0 release. They compromise the maintainer's PyPI credentials, perhaps through a leaked API key found in a public repository or a credential stuffing attack.
Once inside, the attacker does not touch the existing source distribution. They simply upload a new, malicious wheel to the 1.0.0 release. They tag it to support common systems, like modern Linux and Windows environments.
The next time a developer runs their deployment pipeline, pip looks for package==1.0.0. It sees the new wheel. Since a wheel is preferred over the old source distribution, pip downloads the malicious wheel. The attacker's code runs during the installation process, granting them access to the build server or production environment.
The developer has no idea they have been compromised. They did not change their dependency file. They did not upgrade the package version. The build log still shows it installed version 1.0.0. The attack happened entirely behind the scenes because PyPI allowed new files to be attached to old releases indefinitely.
Closing the Window: The 14-Day Rule
PyPI's new policy directly addresses this threat. Once a release is created, maintainers have a strict 14-day window to upload any additional distribution files for that release. Once those 14 days pass, the release is permanently locked. You cannot upload a new wheel, and you cannot upload a new source distribution.
If you try to upload a file after the window closes, PyPI's API will reject it with an error. The only way to distribute new files is to create a new release version.
Choosing a 14-day window instead of making releases instantly immutable accommodates the complexity of modern build systems. Many maintainers use automated build systems that run across multiple platforms. Building wheels for macOS and Windows can take time, especially if a build agent fails or a compiler issue needs manual troubleshooting. A 14-day window gives maintainers plenty of time to resolve these build issues and upload all necessary files.
But it prevents attackers from targeting old, unmonitored releases. If an attacker gains access to an account, they can no longer poison a release that was published months or years ago. They are forced to publish a new version, which is much easier to detect and flag.
How Maintainers Must Adapt
For most projects, this change will not disrupt daily operations. Modern CI/CD pipelines usually build and upload all files within a few hours of tag creation.
But there are scenarios where maintainers will need to change their habits.
Consider the release of a new Python version, such as Python 3.13. In the past, a maintainer might have decided to build Python 3.13 wheels for older, stable versions of their library. This allowed users on older versions to adopt the new Python runtime without upgrading the library itself.
Under the new rules, this is impossible if the older library version is more than 14 days old. The maintainer must release a new patch version of the library that includes the new wheels. While this requires a bit more effort, it is ultimately a safer practice. It ensures that a version tag on PyPI remains a reliable representation of a specific point in time.
Another challenge involves projects with manual release steps. If a maintainer uploads a source distribution from their laptop and plans to upload wheels later when they have access to a different machine, they must do so within two weeks. If they forget, they will have to bump the version number.
Moving to Trusted Publishers
To make package management even more secure, PyPI encourages maintainers to use Trusted Publishers. This authentication method uses OpenID Connect (OIDC) to link PyPI directly to code repositories on platforms like GitHub or GitLab.
Historically, maintainers had to generate a PyPI API token and store it as a secret in their repository. If an attacker gained access to the repository settings or compromised the CI/CD platform, they could steal the token and upload malicious files.
With Trusted Publishers, you do not store any long-lived secrets. When your GitHub Actions workflow runs, it requests a temporary identity token from GitHub. PyPI verifies this token against a pre-configured trust relationship. If the token matches, PyPI issues a short-lived access token that is only valid for the duration of that specific build.
Combining Trusted Publishers with the 14-day upload limit creates a double barrier for attackers. First, they cannot easily steal credentials because there are no permanent secrets to compromise. And even if they manage to hijack a workflow run, they cannot modify historical releases.
The Broader Push for Supply Chain Security
PyPI's 14-day rule is not an isolated experiment. It is part of a coordinated effort across the open-source ecosystem to secure software supply chains.
For years, package registries operated on a model of maximum convenience. Anyone could publish anything, and files could be modified or added with minimal friction. This open model helped open-source software grow rapidly, but it also made registries a prime target for supply chain attacks.
We are now seeing the consequences of that open model. Attackers regularly target registries like npm and PyPI to distribute malware and steal credentials.
In response, registries are tightening controls. PyPI now requires two-factor authentication for all maintainers. They have automated malware detection systems that scan new uploads. They have restricted the use of certain package names to prevent typosquatting. The 14-day rule is the latest tool in this security toolkit.
It represents a shift in thinking. We are moving away from the idea that package registries should be completely flexible storage systems. Instead, we are treating them as critical infrastructure that must prioritize integrity and predictability.
Best Practices for Developers
If you consume Python packages, this change helps protect you from silent dependency poisoning. But you still need to follow good security habits.
First, use a dependency locker. Tools like Poetry or Pipenv generate lock files that contain cryptographic hashes of every package file. When you install dependencies, the package manager verifies that the hash of the downloaded file matches the hash in the lock file. This ensures that even if someone manages to upload a malicious file within the 14-day window, your build will fail because the hash will not match.
Second, monitor your dependencies for updates. While the 14-day rule protects old versions, attackers can still publish malicious new versions. Automated tools can alert you when a dependency is updated, allowing you to review the changes before pulling them into your codebase.
For maintainers, the path forward is clear. Automate your release process using Trusted Publishers. Build all your wheels in a single, automated workflow run. This ensures that all distribution files are uploaded to PyPI at the same time, well within the 14-day window.
Ecosystem security is a shared responsibility. While PyPI's infrastructure changes make the platform safer, it is up to developers and maintainers to adopt the tools and practices that keep their code secure. The 14-day rule is a welcome change that closes a major security gap, making the entire Python ecosystem a little more resilient against supply chain threats.



