How to Protect Against Python Supply Chain Attacks with uv
Malicious packages uploaded to PyPI are usually pulled within hours or days, either yanked or deleted outright. A dependency cooldown tells uv to ignore packages published within a recent window, holding a compromised release out of your resolution long enough for someone else to catch it.
Set a dependency cooldown
The exclude-newer setting accepts durations and timestamps. Security hardening wants a duration, which creates a rolling window that moves forward with the current date. Rolling the cutoff forward with a duration covers every value the option takes.
On the command line
uv lock --exclude-newer "7 days"
uv sync --exclude-newer "7 days"This form is a one-off. uv does not store it, so the next plain uv sync re-resolves without the cooldown. Use configuration for a cooldown you expect to hold.
In project configuration
Add the setting to pyproject.toml or uv.toml so it applies to every uv lock and uv sync in the project:
# pyproject.toml
[tool.uv]
exclude-newer = "7 days"# uv.toml
exclude-newer = "7 days"Use one or the other. A uv.toml beside pyproject.toml makes uv ignore [tool.uv] entirely, so a cooldown set there stops applying.
In user-level configuration
Apply the cooldown to all projects on a machine by adding it to the user-level uv.toml:
# ~/.config/uv/uv.toml
exclude-newer = "7 days"Enforce the cooldown in CI
Keep the cooldown in configuration rather than a pipeline-only flag, so developers and CI resolve against the same window. Then install from the lockfile:
uv sync --locked--locked fails the job when pyproject.toml and uv.lock disagree, catching a dependency added without a fresh uv lock:
$ uv sync --locked
Resolved 3 packages in 2ms
error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided.
hint: To update the lockfile, run `uv lock`.
A rolling window does not trip this check by itself. uv records the span in uv.lock rather than the timestamp it resolved to, so uv sync --locked and uv lock --check both pass on a freshly locked project.
Let an urgent patch through the cooldown
A cooldown delays security fixes along with everything else. When the patch you need is younger than the window, exempt that one package with exclude-newer-package while the rest of the tree keeps its buffer:
[tool.uv]
exclude-newer = "7 days"
exclude-newer-package = { my-package = false } # install my-package regardless of ageEach exemption trades the buffer for speed on one package, so remove it once the window catches up. Overriding the cutoff for one package covers the other values the field takes.
Choose a cooldown period
7 days is a practical default. Most malicious uploads are detected within this window, and most projects can wait a week for new dependency versions. The litellm supply chain attack in March 2026 was discovered and yanked within hours, so a 7-day buffer would have been more than enough.
30 days suits production deployments and internal tooling where stability matters more than access to recent releases.
Diagnose cooldown failures
Read past the solver error
When the cooldown excludes every candidate, the solver error reads like an ordinary version conflict, and only the hint below it names exclude-newer as the cause. Read past the No solution found block before assuming the constraint is wrong. Reading that hint covers the output in full.
Check whether the index publishes upload times
The cooldown needs the upload-time field defined in PEP 700, which PyPI publishes and many private indexes do not. Exempting an index that does not publish upload times shows the failure and the fix.
Tip
Block known malware at install time
A cooldown buys time for malicious uploads to be detected. uv’s malware check acts on the detections themselves. With the check enabled, every command that syncs the environment (uv add, uv sync, and others) looks up the locked packages against the MAL advisories in the OSV database, sourced from the OpenSSF malicious-packages project. A match stops the sync before the package installs, so none of its code runs.
In project configuration
In pyproject.toml, add [tool.uv.audit]; in uv.toml, add [audit] to enable the check for every sync in the project:
# pyproject.toml
[tool.uv.audit]
malware-check = true# uv.toml
[audit]
malware-check = trueTo query a custom advisory database instead of the default OSV endpoint, add malware-check-url:
# pyproject.toml
[tool.uv.audit]
malware-check = true
malware-check-url = "https://your-internal-advisory-db.example.com"With an environment variable
For CI pipelines or one-off sessions, set UV_MALWARE_CHECK instead:
export UV_MALWARE_CHECK=1With the check enabled, uv prints an experimental-feature warning on every sync. A clean lookup adds nothing else, so an ordinary install means nothing matched:
$ uv sync
Resolved 2 packages in 2ms
warning: Malware checks are experimental and may change without warning. Pass `--preview-features malware-check` to disable this warning.
Installed 1 package in 4ms
+ six==1.17.0
The check only blocks malware already recorded as an advisory. The cooldown covers the gap between a malicious upload and its advisory, so run both.
Learn More
- uv: A Complete Guide covers what uv does, how fast it is, the core workflows, and recent releases.
--exclude-newerfor reproducible environments documents every value the option takes, what uv writes into the lockfile, and pip’s equivalent flag.- How to Scan Python Dependencies for Vulnerabilities covers
uv audit, the after-the-fact companion to these install-time defenses. - LiteLLM supply chain attack and securing Python dependencies
- uv documentation on dependency cooldowns
- Vulnerability and malware checks in uv is Astral’s announcement of the malware check and
uv audit.