What is a Dependency Cooldown?
A dependency cooldown tells a package manager to ignore any release published within the last few days. Instead of resolving to the newest version the instant it appears, the resolver waits until each candidate has existed in public for a set period.
The point is timing. When an attacker publishes a compromised version of a popular package, security researchers and registry teams usually find it fast. A cooldown makes sure you are not the one who installs it in the gap before they do.
Why delay an upgrade you could take now?
Most malicious uploads to PyPI are pulled within hours, either yanked or deleted outright. The litellm compromise went that way on a package pulling roughly 95 million downloads a month. Outside Python the pattern repeats: the Axios compromise was discovered about three hours after release, and the s1ngularity packages were live for roughly four. In every case, a resolver that refused to install anything newer than a few days old would never have seen the malicious version.
That is the whole trade a cooldown makes. You give up immediate access to brand-new releases, and in return you let the community vet each version before it can enter your dependency tree. For a supply-chain attack that lives for hours, a window measured in days is decisive.
What yanking does once the bad version is found
Yanking withdraws a release without deleting it. Under PEP 592, the spec that defines yanking, the file stays downloadable so a lockfile that already pins the version keeps installing, while a resolver choosing among candidates passes it over.
uv reads that flag from the index as it stands today, not as it stood at the cutoff. Lock a project that depends on urllib3 with a cutoff of April 27, 2023, and uv resolves to 1.26.15 rather than the 2.0.0 release published the day before, because 2.0.0 was yanked after that cutoff and uv honors the yank anyway.
The cooldown and the yank are two filters a candidate has to clear, and an exact pin drops one of them. Ask for urllib3==2.0.0 and uv installs it, warning that the version is yanked and quoting the maintainer’s reason.
Fixed date or rolling window?
A cooldown is one of two things a time filter can express, and the difference matters.
A fixed date pins resolution to a point in the past. “Resolve as if it were June 15, 2023” produces the same dependency graph every time, forever. That is the tool for reproducing an old environment or bisecting when a dependency started failing, and it is covered in How to Use --exclude-newer for Reproducible Python Environments.
A rolling window is anchored to the present instead. “Ignore anything published in the last seven days” trails the current date, so the cutoff advances as time passes. This is the cooldown: a moving buffer that always keeps the newest releases out until they age past the threshold.
uv expresses both through the same exclude-newer setting on uv. A timestamp gives the fixed date; a duration such as "7 days" gives the rolling window.
Why the window stops moving once you lock
A rolling window sounds like it should slide forward on every command. It does not.
When uv resolves with a duration, it records the duration in uv.lock as exclude-newer-span rather than the timestamp that duration resolved to. Subsequent uv sync runs install the versions the lockfile already pins and never recompute “seven days ago.” The window advances only when a fresh resolution runs, such as uv lock --upgrade, uv lock --refresh, or uv add.
Whether the cooldown survives that far is a separate question. A duration set in pyproject.toml applies to every later command; one passed on the command line does not, and the next plain uv sync re-resolves without it.
The behavior is deliberate. A cooldown protects the moment new versions enter the tree, which is resolution. Once resolution has happened, the lockfile already pins the exact versions, so re-filtering on every install would add nothing but nondeterminism. The cooldown and the lockfile cover different moments, and they work together.
How long should the window be?
Seven days is the common recommendation. It catches the large majority of malicious uploads, which are usually pulled within hours, while asking most projects to wait only a week for new dependency versions. Datadog’s analysis settles on one week as standard guidance, with twelve hours as an aggressive lower bound.
Thirty days suits production deployments and internal tooling, where stability is worth more than fast access to new releases. The cost of a longer window is missing legitimate patches for longer, which is why uv also lets a single package opt out through exclude-newer-package when an urgent fix lands inside the window.
How package managers converged on cooldowns
The idea is not specific to uv or even to Python. Several tools converged on it during the 2026 wave of registry attacks:
- uv reads a duration from
exclude-newer. See How to Protect Against Python Supply Chain Attacks with uv for the configuration. - pip offers
--uploaded-prior-to, which takes a datetime or a duration. - Dependabot waits three days by default before opening a version-update pull request, while still letting security patches through immediately.
- Renovate offers
minimumReleaseAge, and npm, pnpm, and Yarn each gate on a minimum release age.
Every one of these refuses a release until it has survived in public for a set period. The vocabulary differs; the mechanism does not.
Where a cooldown breaks down
A cooldown depends on knowing when each version was published, which comes from the upload-time field defined by PEP 700 index metadata. PyPI publishes this field. Custom or private indexes may not, and uv treats an artifact it cannot date as unavailable, so resolution fails rather than quietly waving those packages through. Exempt the index with exclude-newer = false inside its [[tool.uv.index]] block, shown in How to Use --exclude-newer for Reproducible Python Environments.
A cooldown also does not identify malware; it only buys time for someone else to. It pairs naturally with an install-time check against known-malicious advisories, which acts on the detections a cooldown is waiting for. Treat the cooldown as one layer, not the whole defense.
Learn More
- How to Protect Against Python Supply Chain Attacks with uv shows how to set a cooldown, exempt an urgent patch, and enable the malware check.
- What is a Python Supply Chain Attack? explains the threat a cooldown defends against.
- How to Ignore a Dependency in Dependabot Without Blocking Its Security Updates covers the heavier option for a package a cooldown cannot help, and why it silences security updates unless scoped.
- Dependency Cooldowns tracks cooldown support across package managers.
- The case for dependency cooldowns from Datadog Security Labs walks through the attack timelines behind the seven-day recommendation.