How to Use uv exclude-newer for PyPI Supply Chain Security
Problem
I woke up to a security alert. A package I installed yesterday had been flagged as malicious on PyPI. The attacker had uploaded a compromised version of a popular library just 6 hours before I ran my uv lock command.
Subject: PyPI Security Advisory - Malicious Package Detected
Package: requests-helper v2.1.0Uploaded: 2026-04-01 14:30 UTCDetected: 2026-04-01 20:45 UTCStatus: Removed from PyPI
Your project last resolved dependencies: 2026-04-01 15:00 UTCAction Required: Re-run uv lock immediatelyThe timing was terrifying. The malicious package was uploaded, and within 90 minutes my CI/CD pipeline had locked it into my project. I had no defense against this.
This got me thinking: how do I protect against packages that haven’t been vetted yet?
What’s Happening?
PyPI supply chain attacks follow a predictable pattern. Attackers know that newly published packages have a window of vulnerability - the time before the community detects and removes them.
┌─────────────────────────────────────────────────────────────────┐│ Attack Lifecycle │├─────────────────────────────────────────────────────────────────┤│ T+0h Malicious package uploaded to PyPI ││ T+1h Automated scanners start analyzing ││ T+2h Some users already installed the package ││ T+4h Community reports start appearing ││ T+6h Package flagged, PyPI administrators notified ││ T+8h Package removed from PyPI ││ T+10h Security advisories published ││ ││ The vulnerable window: T+0h to T+6h ││ Most attacks detected within 24-48 hours │└─────────────────────────────────────────────────────────────────┘The critical insight: most malicious packages are caught within hours or days. If I could just wait before consuming new packages, I’d let the community do the vetting for me.
A Reddit discussion on r/Python confirmed this approach. The top comment with 76 upvotes was straightforward:
“For uv, in pyproject.toml:
[tool.uv] exclude-newer = '2 weeks'”
Another comment added context: “Use exclude-newer type of feature to avoid consuming the very latest packages - most vulnerabilities are found in a few hours.”
The community consensus was clear: a dependency cooldown is the solution.
The Solution: exclude-newer
uv’s exclude-newer configuration creates a “dependency cooldown.” It tells uv to ignore any package version published within a specified time window.
Let me try the basic configuration:
[tool.uv]exclude-newer = "2 weeks"Now when I run uv lock, it will only consider packages published more than 14 days ago:
$ uv lockResolved 42 packages in 234ms# All packages are at least 2 weeks old# Any package published in the last 14 days is excludedThis gives the community time to detect and remove malicious packages before I even consider them.
How It Works
Let me trace through what happens when uv resolves dependencies with exclude-newer:
┌─────────────────────────────────────────────────────────────────┐│ uv lock with exclude-newer │├─────────────────────────────────────────────────────────────────┤│ 1. Parse exclude-newer configuration ││ └─ Convert "2 weeks" to cutoff timestamp ││ └─ Example: 2026-04-02 - 14 days = 2026-03-19 ││ ││ 2. Query PyPI for package metadata ││ └─ Fetch upload timestamps for all versions ││ ││ 3. Filter available versions ││ └─ Exclude versions uploaded after cutoff ││ └─ Only consider "vetted" packages ││ ││ 4. Resolve dependencies ││ └─ Use SAT solver on filtered versions ││ └─ Generate uv.lock with safe packages │└─────────────────────────────────────────────────────────────────┘The cutoff calculation happens automatically:
Configuration: exclude-newer = "2 weeks"Current time: 2026-04-02 11:32:00 UTCCutoff time: 2026-03-19 11:32:00 UTC
Package versions EXCLUDED (published after cutoff): - requests v2.32.0 (published 2026-03-25) -> EXCLUDED - numpy v2.2.0 (published 2026-03-20) -> EXCLUDED
Package versions INCLUDED (published before cutoff): - requests v2.31.0 (published 2026-03-01) -> INCLUDED - numpy v2.1.0 (published 2026-03-15) -> INCLUDEDWhy This Matters
Traditional security approaches focus on vulnerability scanning AFTER installation. exclude-newer shifts security LEFT:
┌─────────────────────────────────────────────────────────────────┐│ Traditional Approach (Reactive) │├─────────────────────────────────────────────────────────────────┤│ 1. Install packages ││ 2. Run vulnerability scanner ││ 3. Detect issues ││ 4. Update/remove problematic packages ││ ││ Problem: You already installed the malicious package ││ Problem: Scanner might not detect new attacks │└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐│ exclude-newer Approach (Proactive) │├─────────────────────────────────────────────────────────────────┤│ 1. Set cooldown period ││ 2. Resolve dependencies (new packages excluded) ││ 3. Install vetted packages only ││ 4. Community has already caught malicious packages ││ ││ Benefit: Malicious packages never enter your project ││ Benefit: Leverage community vetting as free security layer │└─────────────────────────────────────────────────────────────────┘This is particularly important because:
- Lockfiles alone don’t protect against new attacks - A fresh
uv lockcan pick up a malicious package uploaded 5 minutes ago - Pinning versions requires discipline - Casual
uv lock --upgradecan expose you to new attacks - Cooldown provides automatic protection - No manual intervention needed
Configuration Options
Relative Time (Recommended for Active Projects)
[tool.uv]exclude-newer = "2 weeks"Supported time units: seconds, minutes, hours, days, weeks
I recommend 1-2 weeks for most projects. This balances security with practical dependency updates.
Specific Timestamp (Recommended for Releases)
[tool.uv]exclude-newer = "2026-03-15T00:00:00Z"Use RFC 3339 format for exact reproducibility. This is ideal for production releases where you want to pin to a specific vetting point.
Per-Package Override
Some packages update frequently and have strong security practices. You can apply different cooldowns per package:
[tool.uv]exclude-newer = "1 week"exclude-newer-package = { setuptools = "30 days" }This applies a stricter 30-day cooldown to setuptools while using 1 week for other packages.
CLI Usage
You can also use exclude-newer directly from the command line:
# Lock with a specific cutoff dateuv lock --exclude-newer "2026-03-15T00:00:00Z"
# Run with locked dependenciesuv run --locked python main.pyInline Script Configuration
For standalone Python scripts with inline dependencies (PEP 723), you can also set exclude-newer:
# /// script# dependencies = [# "requests",# "pandas",# ]# [tool.uv]# exclude-newer = "2026-03-15T00:00:00Z"# ///
import requestsimport pandas as pd
def fetch_data(): response = requests.get("https://api.example.com/data") return pd.DataFrame(response.json())
if __name__ == "__main__": df = fetch_data() print(df.head())Run it with:
$ uv run script.py# uv respects the inline exclude-newer configurationMy Trial-and-Error Process
I didn’t get this right the first time.
Attempt 1: Too Short Cooldown
[tool.uv]exclude-newer = "1 day"A 1-day cooldown provides minimal protection. Most attacks are detected within 24-48 hours, but some take up to a week. I was still exposed.
Attempt 2: Forgot to Update Gradually
[tool.uv]exclude-newer = "2026-01-01T00:00:00Z"I set a fixed date and forgot about it. Three months later, my project was stuck on ancient packages. Missing legitimate security fixes.
The fix: set reminders to periodically update:
# Every 2 weeks, bump the cutoff forward# From: exclude-newer = "2026-01-15"# To: exclude-newer = "2026-01-29"Or use relative time:
[tool.uv]exclude-newer = "2 weeks" # Automatically recalculated each lockAttempt 3: Didn’t Combine with Lockfile
$ uv lock$ git add pyproject.toml$ git commit -m "Add exclude-newer"# Forgot to commit uv.lock!The exclude-newer setting determines WHAT packages are allowed, but uv.lock records WHICH specific versions. Without committing the lock file, my CI/CD might resolve different versions.
$ uv lock$ git add pyproject.toml uv.lock$ git commit -m "Add exclude-newer with lock file"Common Mistakes
Mistake 1: Setting Cooldown Too Short
# WRONG - Minimal protectionexclude-newer = "1 day"
# CORRECT - Meaningful protectionexclude-newer = "2 weeks"Most malicious packages are detected within 1-2 weeks. A shorter cooldown barely helps.
Mistake 2: Not Committing Lock Files
# WRONG - Lock file not committed$ echo "uv.lock" >> .gitignore
# CORRECT - Always commit lock file$ git add uv.lockexclude-newer works with lock files. Without the lock file, protection is incomplete.
Mistake 3: Overriding Without Justification
# WRONG - Why does requests need a shorter cooldown?[tool.uv]exclude-newer = "2 weeks"exclude-newer-package = { requests = "1 day" }
# CORRECT - Only override when necessary[tool.uv]exclude-newer = "2 weeks"# No overrides unless you have a specific reasonPer-package overrides should be rare and justified.
Mistake 4: Mixing with Regular Upgrade Habits
# WRONG - Bypasses your protection$ uv lock --upgrade
# CORRECT - Respect the cooldown$ uv lock # No --upgrade flagThe --upgrade flag can override exclude-newer behavior. Use it carefully.
The Workflow I Now Use
After my security scare, I established this workflow:
┌─────────────────────────────────────────────────────────────────┐│ Project Setup │├─────────────────────────────────────────────────────────────────┤│ 1. Initialize project ││ $ uv init my-project ││ ││ 2. Add exclude-newer to pyproject.toml ││ [tool.uv] ││ exclude-newer = "2 weeks" ││ ││ 3. Add dependencies ││ $ uv add requests pandas numpy ││ ││ 4. Commit everything ││ $ git add pyproject.toml uv.lock ││ $ git commit -m "Initialize with exclude-newer" │└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐│ Regular Updates │├─────────────────────────────────────────────────────────────────┤│ 1. Every 2 weeks: ││ $ uv lock # Cutoff automatically moves forward ││ $ git add uv.lock ││ $ git commit -m "Update dependencies" ││ ││ 2. If a critical security fix is needed: ││ $ uv add package@specific-version ││ # Override exclude-newer for this specific version │└─────────────────────────────────────────────────────────────────┘Why This Matters for Teams
The exclude-newer approach scales well for teams:
1. Consistent Security Standard - Every team member has the same cooldown - No one accidentally installs fresh packages
2. Reproducible Builds - Same cutoff timestamp = same package versions - CI/CD uses identical dependencies as developers
3. Reduced Security Alert Response Time - When alerts arrive, you're likely already safe - No emergency "did we install that?" investigations
4. Clear Update Policy - "We wait 2 weeks before adopting new versions" - Simple rule everyone understandsThe Reason This Feature Exists
uv’s developers at Astral recognized that the traditional dependency model was optimized for convenience, not security. In the early Python days, PyPI was small and trusted. Today, with thousands of packages and frequent attacks, blind trust is dangerous.
The design philosophy:
1. Security through delay, not through scanning2. Leverage community vetting as a free security layer3. Make security automatic, not manual4. Balance protection with practical workflowThis aligns with how other ecosystems think about security. Debian’s “stable” releases use similar approaches - packages are vetted over time before inclusion.
Summary
uv’s exclude-newer creates a dependency cooldown that protects against PyPI supply chain attacks by ignoring packages published within a specified time window. The recommended configuration:
[tool.uv]exclude-newer = "2 weeks"This configuration:
- Gives the community 14 days to detect and remove malicious packages
- Maintains reproducible builds with committed lock files
- Balances security with practical dependency updates
- Works automatically without manual intervention
After my security scare, I now use this in every project. The next time a malicious package is uploaded to PyPI, I’ll have a 2-week buffer before it can even enter my consideration set. That’s the kind of security that lets me sleep better.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 uv Official Documentation - Excluding Newer
- 👨💻 Reddit: PyPI Supply Chain Attack Prevention Discussion
- 👨💻 Astral uv GitHub Repository
- 👨💻 PyPI Supply Chain Attack Statistics
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments