Supply Chain Security: GitHub & PyPI’s New Protection Measures Against Malicious Code
Supply chain attacks have become one of the most dangerous threats in modern software development. Attackers compromise legitimate packages to inject malicious code that propagates to thousands of downstream projects automatically.
Recent high-profile incidents—from the event-stream npm backdoor to the ua-parser-js trojan—demonstrate how a single compromised package can cascade across the entire software ecosystem within hours.
In response to this escalating threat, GitHub and PyPI have introduced new defensive measures that fundamentally change how dependency updates and package releases work. These changes prioritize security over convenience, introducing deliberate delays that give the security community time to detect and respond to malicious code before it spreads.
In this guide, we’ll explore what these new protections are, how they work, why they matter, and what developers need to know about adjusting their workflows.
The Supply Chain Attack Problem
How Supply Chain Attacks Work
A supply chain attack targets the software development pipeline itself, rather than end-user applications. The attack pattern typically follows these steps:
- Compromise: Attacker gains control of a legitimate package (through stolen credentials, maintainer account takeover, or social engineering)
- Injection: Malicious code is inserted into a new version of the package
- Propagation: Automated dependency update tools (like Dependabot, Renovate, Greenkeeper) pull the malicious version
- Execution: The malicious code runs in CI/CD pipelines, production servers, or developer machines
The speed of propagation is the critical factor. Automated update tools can spread a compromised package to thousands of projects within hours, often before security researchers detect the malicious code.
Real-World Examples
event-stream (2018): Attacker gained control of a popular npm package with 2 million weekly downloads, injected code to steal Bitcoin wallet credentials. The malicious version remained undetected for two months.
ua-parser-js (2021): Compromised npm package with 9 million weekly downloads. Attackers injected cryptocurrency miners and password stealers. Detected within hours, but not before hundreds of projects auto-updated.
ctx and phpass (2022): Typosquatting attack on PyPI. Malicious packages designed to look like legitimate ones, stealing AWS credentials when installed.
These incidents share a common pattern: the window between compromise and detection is where the damage occurs. Automated systems propagate malicious code faster than human security review can catch it.
GitHub’s Dependabot 3-Day Update Delay
What Changed
GitHub’s Dependabot—the automated dependency update tool used by millions of repositories—now introduces a 3-day waiting period before issuing pull requests for newly published package versions.
Previously, Dependabot would create PRs for new package versions within hours of publication. Now, it waits 72 hours before creating update PRs for packages it monitors.
How It Works
When a new version of a dependency is published:
- Day 0: Package maintainer publishes version 2.5.0 to npm, PyPI, RubyGems, etc.
- Days 1-3: Dependabot observes the new version but does not create update PRs
- Day 3+: If the version remains available and unrevoked, Dependabot creates the update PR
This delay applies to all new package versions, not just those flagged as suspicious.
Why This Matters
The 3-day window serves several critical purposes:
1. Detection Window
Security researchers, automated scanners, and the community have time to analyze new versions before they propagate via Dependabot. Many malicious packages are detected and removed within 24-48 hours.
2. Revocation Time
If a compromised package is detected, maintainers can revoke or unpublish the malicious version before Dependabot spreads it.
3. Behavioral Analysis
Security tools can monitor the package’s behavior in sandbox environments, looking for suspicious network activity, file system access, or obfuscated code.
4. Reduced Blast Radius
By the time Dependabot creates PRs, early adopters and manual updaters have already installed the package. If issues emerge, the community knows about them before automated tools spread the package widely.
What This Means for Developers
For most projects: No immediate action required. Dependabot updates continue automatically, just with a 3-day delay.
For security-critical projects: You might want to:
- Monitor dependency releases manually for critical security patches
- Subscribe to security mailing lists for your key dependencies
- Have a process for manual emergency updates that bypass Dependabot
For emergency patches: If a critical zero-day vulnerability is fixed in a new package version, you can still update manually without waiting for Dependabot. The 3-day delay only affects automated PRs, not manual dependency updates.
# Manual emergency update (bypasses Dependabot delay)
npm install package-name@latest
# or
pip install --upgrade package-name
# Then commit and push immediately
git add package.json package-lock.json # or requirements.txt
git commit -m "Emergency update: package-name security patch"
git push
Exceptions and Edge Cases
The 3-day delay does not apply to:
- Security advisories: If a package version is published with an associated GitHub Security Advisory, Dependabot may create the PR immediately
- Manual updates: Developers can still manually update dependencies anytime
- Existing versions: The delay only applies to newly published versions, not versions that were already available when you added the dependency
PyPI’s 14-Day New-File Rejection Policy
What Changed
The Python Package Index (PyPI) now rejects uploads of new files to releases older than 14 days.
If a package maintainer published version 1.5.0 on July 1st, they cannot add new distribution files (wheels, source distributions, platform-specific builds) to that 1.5.0 release after July 15th.
How It Works
When a package maintainer uploads files to PyPI:
- Initial release: Maintainer uploads
package-1.5.0.tar.gzandpackage-1.5.0-py3-none-any.whlon July 1st - Within 14 days: Maintainer can add additional files (e.g., platform-specific wheels for Windows, macOS, Linux)
- After 14 days: Uploads of new files to the 1.5.0 release are rejected. Maintainer must create version 1.5.1 instead.
Critically, the policy targets new files added to old releases, not edits to existing files (which PyPI never allowed) or new releases altogether.
Why This Matters
The 14-day policy prevents a specific supply chain attack vector: retroactive package poisoning.
In this attack pattern:
- Attacker compromises a maintainer account
- Instead of publishing a new version (which might be noticed), they upload a malicious file to an old, stable release
- Users who install that version—even pinned versions—now pull the malicious file
- Since the version number didn’t change, automated checks don’t flag it
Historical example: An attacker could compromise a package and upload a malicious wheel to version 2.3.0 (published 6 months ago). Projects with package==2.3.0 in their requirements.txt would unknowingly install the poisoned version.
The 14-day cutoff stops this attack. After 14 days, an old release is “frozen”—no new files can be added. Attackers must publish a new version instead, which triggers scrutiny.
What This Means for Developers
For package maintainers:
You must upload all distribution files within 14 days of publishing a release. This includes:
- Source distributions (
sdist) - Pure Python wheels
- Platform-specific wheels (Windows, macOS, Linux)
- Architecture-specific builds (x86_64, ARM, etc.)
If you discover you need to add a platform-specific build after 14 days, you must publish a new patch version instead.
# OLD WORKFLOW (no longer possible after 14 days):
twine upload dist/package-1.5.0-cp311-cp311-win_amd64.whl # Rejected if >14 days old
# NEW WORKFLOW (required after 14 days):
# Bump to 1.5.1 instead
python setup.py sdist bdist_wheel
twine upload dist/package-1.5.1-*
For package consumers:
Virtually no impact. The policy prevents retroactive poisoning without affecting normal installation workflows.
For CI/CD pipelines:
If your pipeline builds distribution files over time (e.g., building Windows wheels after Linux wheels), ensure all builds complete within the 14-day window.
Exceptions and Edge Cases
The 14-day policy applies to new files only. Maintainers can still:
- Publish new versions anytime (1.5.1, 2.0.0, etc.)
- Delete existing files from old releases (though this is discouraged)
- Yank entire releases (marking them as unsuitable for installation)
How These Changes Work Together
GitHub’s Dependabot delay and PyPI’s file rejection policy address different parts of the supply chain attack surface:
| Attack Vector | Mitigation |
|---|---|
| Newly published malicious version | Dependabot 3-day delay provides detection window |
| Retroactive poisoning of old releases | PyPI 14-day policy freezes old releases |
| Typosquatting and malicious new packages | Both policies give security researchers time to flag packages |
| Compromised maintainer credentials | Both policies slow down attack propagation |
Together, they create time barriers that disrupt the speed advantage attackers rely on. They don’t prevent attacks entirely, but they dramatically reduce the window of opportunity.
Should You Adjust Your Workflow?
For most developers, these changes require minimal adjustment. They operate in the background and improve security without disrupting normal workflows.
However, consider these proactive steps:
1. Review Your Dependency Update Strategy
If you rely entirely on Dependabot for updates, you might want to:
- Subscribe to security mailing lists for critical dependencies
- Monitor GitHub Security Advisories for your stack
- Have a process for manual emergency updates
2. Pin Critical Dependencies
For production environments, consider pinning exact versions rather than using version ranges:
# Loose pinning (automatically updates within range)
requests>=2.28.0,<3.0.0
# Exact pinning (only updates when you explicitly change it)
requests==2.31.0
Exact pinning ensures Dependabot PRs don’t auto-merge until you review them. Combine this with the 3-day delay for maximum control.
3. Use Lock Files
Lock files (e.g., package-lock.json, Pipfile.lock, Cargo.lock) ensure reproducible builds and make dependency changes explicit:
# Node.js
npm ci # Installs exact versions from lock file
# Python
pip install -r requirements.txt --require-hashes # Verify integrity
# Rust
cargo build # Uses Cargo.lock by default
4. Monitor Your Dependencies
Use tools that alert you to new versions and known vulnerabilities:
- npm audit / pip-audit: Scan for known vulnerabilities
- Snyk / Dependabot alerts: Real-time security notifications
- Socket.dev / Phylum: Supply chain security monitoring
# Check for vulnerabilities
npm audit
pip-audit
# Example output
┌───────────────┬──────────────────────────────────────────────────────────┐
│ Package │ lodash │
│ Severity │ high │
│ Patched in │ >=4.17.21 │
│ Recommendation│ Update to 4.17.21 or later │
└───────────────┴──────────────────────────────────────────────────────────┘
5. Implement Multi-Layered Verification
Don’t rely solely on Dependabot or PyPI’s protections. Add your own verification layers:
# GitHub Actions: Verify dependencies before merging Dependabot PRs
name: Dependency Review
on:
pull_request:
paths:
- 'package.json'
- 'requirements.txt'
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run dependency review
uses: actions/dependency-review-action@v3
with:
fail-on-severity: moderate
This GitHub Action automatically scans Dependabot PRs for known vulnerabilities and blocks merges if high-severity issues are detected.
The Bigger Picture: Shifting Left on Supply Chain Security
The Dependabot delay and PyPI policy represent a broader industry shift toward proactive supply chain security:
Emerging Best Practices
SBOM (Software Bill of Materials): Many organizations now require dependency inventories for compliance and security audits.
Signature Verification: Package managers increasingly support cryptographic signatures (e.g., npm’s signatures, PyPI’s PEP 458).
Dependency Attestation: GitHub now generates signed attestations for npm packages, proving provenance.
Zero-Trust Dependencies: Treat all dependencies as potentially malicious until proven otherwise.
The Cost of Convenience
Automated dependency updates are convenient, but they sacrifice security review time. The 3-day Dependabot delay and 14-day PyPI freeze rebalance this tradeoff.
Developers used to instant updates might find the delays frustrating, but they reflect a necessary reality: security cannot be fully automated. Human review and community vigilance remain essential.
Practical Example: Emergency Security Patch Workflow
When a critical zero-day vulnerability is disclosed and patched:
- Immediate manual update (don’t wait for Dependabot)
# Check current version
npm list vulnerable-package
# or
pip show vulnerable-package
# Update immediately
npm install vulnerable-package@patched-version
# or
pip install vulnerable-package==patched-version
# Verify the fix
npm audit
# or
pip-audit
- Test the update locally
npm test
# or
pytest
- Deploy urgently if tests pass
git add package-lock.json # or requirements.txt
git commit -m "Security: Emergency update for CVE-XXXX-XXXX"
git push origin main
- Monitor Dependabot PR when it arrives (3 days later)
Close it as “already fixed manually” or let it auto-close when it detects your manual update.
Limitations of These Protections
While Dependabot delays and PyPI freezes improve security, they are not silver bullets:
❌ They don’t prevent first-day attacks: Malicious code published on Day 0 can still be manually installed before detection.
❌ They don’t catch all malicious code: Sophisticated attackers can evade automated detection.
❌ They don’t protect against typosquatting: A malicious package with a name similar to a popular package can still be installed by accident.
❌ They don’t verify code behavior: A package can be malicious without being flagged, especially if it uses obfuscation or time-delayed payloads.
✅ They do slow down propagation: This gives the security community time to respond.
✅ They do reduce blast radius: Fewer projects are automatically updated before detection.
✅ They do prevent retroactive poisoning: Old releases remain frozen after 14 days.
Conclusion
GitHub’s 3-day Dependabot delay and PyPI’s 14-day file rejection policy represent a critical shift in supply chain security. They prioritize security over speed, introducing deliberate friction that gives the community time to detect and respond to threats.
For most developers, the impact is minimal—updates continue automatically, just with a short delay. For security-critical projects, the changes provide an additional safety layer that complements manual review processes.
Key takeaways:
- Dependabot now waits 3 days before creating PRs for new package versions
- PyPI blocks new files on releases older than 14 days
- Both policies create detection windows that disrupt supply chain attacks
- Manual emergency updates still work—delays only affect automated tools
- These changes are part of a broader industry shift toward proactive supply chain security
Supply chain attacks are evolving, and defenses must evolve with them. These new protections aren’t perfect, but they’re a necessary step toward a more secure software ecosystem.
Source: This article is based on security updates reported by SecurityWeek and published via Slashdot. For the latest information, consult GitHub’s official Dependabot documentation and PyPI’s release policies.