# 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](/blog/litellm-supply-chain-attack-and-securing-python-dependencies/) went that way on a package pulling roughly 95 million downloads a month. Outside Python the pattern repeats: the [Axios compromise](https://securitylabs.datadoghq.com/articles/dependency-cooldowns/) 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](https://pydevtools.com/handbook/explanation/what-is-a-python-supply-chain-attack.md) 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](https://peps.python.org/pep-0592/), 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](https://pydevtools.com/handbook/how-to/how-to-use-exclude-newer-for-reproducible-python-environments.md).

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](https://pydevtools.com/handbook/reference/uv.md). 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](https://pydevtools.com/handbook/explanation/what-is-a-lock-file.md) 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](https://pydevtools.com/handbook/how-to/how-to-protect-against-python-supply-chain-attacks-with-uv.md) for the configuration.
- **[pip](https://pydevtools.com/handbook/reference/pip.md)** 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](https://peps.python.org/pep-0700/). 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](https://pydevtools.com/handbook/how-to/how-to-use-exclude-newer-for-reproducible-python-environments.md).

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](https://pydevtools.com/handbook/how-to/how-to-protect-against-python-supply-chain-attacks-with-uv.md) shows how to set a cooldown, exempt an urgent patch, and enable the malware check.
- [What is a Python Supply Chain Attack?](https://pydevtools.com/handbook/explanation/what-is-a-python-supply-chain-attack.md) explains the threat a cooldown defends against.
- [How to Ignore a Dependency in Dependabot Without Blocking Its Security Updates](https://pydevtools.com/handbook/how-to/how-to-ignore-a-dependency-in-dependabot-without-blocking-security-updates.md) covers the heavier option for a package a cooldown cannot help, and why it silences security updates unless scoped.
- [Dependency Cooldowns](https://cooldowns.dev/) tracks cooldown support across package managers.
- [The case for dependency cooldowns](https://securitylabs.datadoghq.com/articles/dependency-cooldowns/) from Datadog Security Labs walks through the attack timelines behind the seven-day recommendation.
