What Happened
On July 8, 2026, the Python Package Index (PyPI) implemented a significant change. You can no longer upload new files to a release that's more than 14 days old. If you need to add a Python 3.14-compatible wheel to a package you released three weeks ago, PyPI will reject it.
This isn't a response to a specific breach. It's a preemptive control to close a window that attackers have been exploiting: injecting malicious files into established, trusted package versions long after their initial release.
Timeline
Pre-July 2026: PyPI allowed unlimited retroactive uploads to any release, regardless of age. Maintainers could add platform-specific wheels, fix distribution errors, or update metadata at any time.
July 8, 2026: PyPI merged the 14-day upload window restriction. New file uploads to releases older than 14 days are now rejected at the API level.
Current state: Analysis of the top 15,000 PyPI packages shows that only 56 uploaded a Python 3.14-compatible wheel more than 14 days after the original release. The restriction affects a statistically negligible portion of legitimate maintenance workflows.
Which Controls Failed or Were Missing
The original PyPI architecture lacked temporal upload controls, creating three exploitable gaps:
No time-based upload restrictions: An attacker who compromised maintainer credentials could add malicious files to releases from months or years ago. Users who pinned to specific versions believed they were safe, but the version they pinned to could be modified after the fact.
Insufficient release integrity verification: PyPI had no mechanism to flag or alert when files were added to old releases. Your dependency scanner might check for new versions, but it wouldn't notice that requests==2.28.0 suddenly gained a new wheel file six months after release.
Missing audit trails for retroactive changes: While PyPI logged uploads, there was no prominent signal to downstream consumers that a release had been modified. The package version number stayed the same, so automated tools saw no change.
What the Relevant Standards Require
NIST 800-53 Rev 5, Control SI-7 (Software, Firmware, and Information Integrity) requires organizations to detect unauthorized changes to software. The control specifically calls for integrity verification mechanisms that can identify when components have been modified. PyPI's old model made this verification nearly impossible for downstream consumers.
ISO/IEC 27001:2022, Annex A.8.31 (Separation of development, test and production environments) addresses the principle of temporal separation. Once something moves to "production" (a published release), modifications should trigger new version numbers, not silent updates to existing versions.
SLSA (Supply Chain Levels for Software Artifacts) Level 2 requires that build processes produce provenance metadata and that this metadata be verifiable. When you can add files to an old release without creating a new version, you break the provenance chain. The original build attestation no longer matches the current release contents.
PCI DSS v4.0.1, Requirement 6.3.2 mandates that custom software be developed securely, including maintaining an inventory of software components. If components can be silently modified, your inventory becomes unreliable. You think you're running package==1.2.3 from January, but you're actually running a version that gained new files in August.
The broader principle across all these standards: immutability in production artifacts. Once you release version 1.2.3, that specific version identifier should reference an unchanging set of files.
Lessons and Action Items for Your Team
Implement your own temporal controls: If you run an internal package repository (Artifactory, Nexus, or a private PyPI mirror), add similar time-based restrictions. Set a window after which releases become immutable. Start with 30 days if you need more flexibility than PyPI's 14-day window.
Review your dependency pinning strategy: Pinning to specific versions isn't enough anymore. Use hash-based verification. In your requirements.txt or poetry.lock, include SHA256 hashes for every package. This ensures you're getting the exact files you tested, even if someone adds new files to that version.
# Not sufficient
requests==2.28.0
# Verifiable
requests==2.28.0 \
--hash=sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983
Monitor for retroactive changes: Set up alerts when packages you depend on receive new file uploads to old releases. This is a signal worth investigating. Tools like pip-audit can help, but you'll need custom scripting to detect when files are added to existing versions versus new versions being released.
Plan your release workflows around immutability: If you maintain packages, adopt a "release early, release often" approach. Don't wait weeks to add platform-specific wheels. Build and upload all your distribution variants within the 14-day window. If you miss the window, cut a new patch version (1.2.4 instead of adding to 1.2.3).
Update your incident response procedures: Add "dependency modification" as a distinct incident category. If you detect that a pinned dependency has changed without a version number change, treat it as a potential compromise. Investigate immediately, don't wait for your next security review cycle.
Evaluate PEP 694 for future planning: The Python Enhancement Proposal 694 will further standardize upload processes and security controls. Track its development and plan to adopt its requirements as they're finalized.
The PyPI restriction is a forcing function. It makes the secure path the only path. Your team should adopt the same philosophy: make it impossible to do the insecure thing, rather than hoping everyone remembers to do the secure thing.



