Dependency Cooldowns¶
In March 2026 alone, three widely-used packages were compromised after attackers gained access to tokens used to publish those packages to their respective package registries. LiteLLM had versions on PyPI that harvested cloud credentials, SSH keys, and Kubernetes configs for about two hours before removal. The Telnyx Python SDK shipped platform-specific backdoors triggered at import time, bypassing install-time detection entirely. And axios, with over 100 million weekly npm downloads, had versions that dropped a remote access trojan via an injected dependency, live for 2–3 hours before npm pulled them.
Anyone who ran pip install or npm install while the malicious packages were available could have infected their
system and potentially exposed sensitive data to the attackers. That's the inherent risk of always resolving to the
latest version of a package at install time. Dependency cooldowns are a relatively simple fix to prevent this from
happening: tell your package manager to ignore any version that hasn't existed for at least N days. Security
researchers and automated scanners catch most compromised packages within hours or days of publication. A cooldown just
makes sure you're not the one who installs it before they do.
Does it actually work?¶
An analysis of ten prominent supply chain attacks found that eight had exploitation windows under one week. All but one lasted under two weeks. Attackers move fast after compromising a project, but they also get caught fast. A three-day cooldown would have blocked most of these. In the August 2025 Nx npm attack, malicious code exfiltrated credentials within a 4–5 hour window before the package was pulled. The LiteLLM compromise mentioned above also had a window of only a few hours (first detected at 10:39 UTC, quarantined on PyPI at 13:38 UTC).
That's roughly an 80-90% reduction in exposure for a simple config change (if the package manager of your choice supports the cooldown feature, see below). All native implementations enforce cooldowns on transitive dependencies too, not just the packages you directly install.
All examples below use a three-day cooldown. Pick whatever number you're comfortable with; even one day makes a real difference.
Python Ecosystem¶
uv¶
uv introduced the built-in cooldown feature in version 0.9.17. It uses relative durations
natively and supports several timestamp and duration formats. For example, the following installation command of package
foo will ignore any versions of this package that are newer than three days:
To make this applicable to all uv commands that install packages, add the following to your ~/.config/uv/uv.toml:
Or as an environment variable:
For project-level config in pyproject.toml:
uv also supports per-package overrides via exclude-newer-package. To exempt a specific package from the cooldown
(e.g. to pull an urgent security fix), set it to false in your pyproject.toml or uv.toml:
There is no CLI flag or environment variable for exclude-newer-package; it can only be set in a config file.
Refer to uv documentation for more information about this configuration setting.
pip¶
pip 26.1 (released April 2026) supports ISO 8601 duration format for the --uploaded-prior-to option. For example,
the following installation command will ignore any versions of package foo that are newer than three days:
As an environment variable (applies to all pip install, pip download, and pip wheel commands):
Or in ~/.config/pip/pip.conf:
pip does not support per-package exemptions. To bypass the cooldown for an urgent fix, install the specific package in a separate command with the environment variable unset:
If you use the shell wrapper function from the pip < 26.1 section, call command pip directly to bypass it.
See pip documentation for more information about this configuration option.
Tools built on pip inherit this too: pip-compile (from pip-tools) exposes
--uploaded-prior-to as a passthrough option and honors PIP_UPLOADED_PRIOR_TO (requires pip >= 26.0).
pip < 26.1¶
Older pip versions (26.0) only accept absolute timestamps for --uploaded-prior-to. Since absolute timestamps go
stale, you need to compute them dynamically. One option is a shell wrapper in your ~/.bashrc (or a shell RC file of
your choice):
pip() {
local pip_major
pip_major=$(command pip --version 2>/dev/null | awk '{ split($2, a, "."); print a[1]; exit }')
case "$1" in
install|download|wheel)
if [[ "${pip_major:-0}" -ge 26 ]]; then
local cutoff
cutoff=$(date -u -d '3 days ago' '+%Y-%m-%dT%H:%M:%SZ')
command pip "$1" --uploaded-prior-to "$cutoff" "${@:2}"
else
echo "warning: pip ${pip_major:-unknown} does not support --uploaded-prior-to (need >= 26), skipping cooldown" >&2
command pip "$@"
fi
;;
*)
command pip "$@"
;;
esac
}
Alternatively, you can set an absolute date in ~/.config/pip/pip.conf and update it automatically with a cron job
(see Seth Larson's blog post covering this
approach).
~/.config/pip/pip.conf:
/usr/local/bin/pip-dependency-cooldown:
#!/usr/bin/python3
import datetime, sys, os, re
def main() -> int:
pip_conf = os.path.abspath(os.path.expanduser(sys.argv[1]))
days = int(sys.argv[2])
with open(pip_conf, "r") as f:
pip_conf_data = f.read()
uploaded_prior_to_re = re.compile(
r"^uploaded-prior-to\s*=\s*2[0-9]{3}-[0-9]{2}-[0-9]{2}$", re.MULTILINE
)
new_date = (datetime.date.today() - datetime.timedelta(days=days)).strftime("%Y-%m-%d")
pip_conf_data = uploaded_prior_to_re.sub(f"uploaded-prior-to = {new_date}", pip_conf_data)
with open(pip_conf, "w") as f:
f.write(pip_conf_data)
return 0
if __name__ == "__main__":
sys.exit(main())
Hourly cronjob:
0 * * * * /usr/local/bin/pip-dependency-cooldown ~/.config/pip/pip.conf 3 2>&1 | logger -t pip-dependency-cooldown
pipenv¶
pipenv added the cool-down-period setting in version 2026.6.2. It is configured in the
[pipenv] section of the project's Pipfile and accepts a duration string in the form <N>d (days). For example, to
apply a three-day cooldown:
Under the hood, pipenv passes this value to pip's --uploaded-prior-to flag during resolution. The filtering only works
against indexes that expose upload-time metadata via the Simple Repository API; when the index does not provide it, the
setting is accepted but has no filtering effect (see Private PyPI registries).
There is no environment variable equivalent and no per-package bypass. To install a specific package without the cooldown, temporarily remove the setting from the Pipfile or install the package directly with pip.
poetry¶
poetry added the
solver.min-release-age setting in 2.4.0.
To set it globally, execute:
Or use the following environment variable:
You can also set the following in your project's pyproject.toml or in ~/.config/pypoetry/config.toml:
To exempt specific packages from the cooldown (e.g. for an urgent CVE fix):
Or via environment variable:
You can also exempt all packages from a specific source (useful for private registries):
If the package registry does not expose upload times for a release, poetry fails open and will allow a release to be installed.
See Private PyPI registries.
PDM¶
PDM added support for exclude-newer in the [tool.pdm.resolution] table of
pyproject.toml in version 2.26.9. It accepts a relative duration (7d, 12h, 3w) or an absolute UTC date or
timestamp. For a three-day cooldown:
To set it globally for all projects (written to PDM's user config, PDM 2.27.0+), run:
Add --local to scope it to the current project's .pdm.toml instead. The same value can also be passed per-command
via pdm lock --exclude-newer 3d. There is no environment variable equivalent.
conda¶
The conda package manager does not have a native cooldown feature, but issue #15759 proposed its implementation.
pixi¶
pixi introduced a built-in cooldown feature in version
0.67.0. It uses relative durations natively and accepts
three formats for exclude-newer:
- an RFC 3339 timestamp (e.g.
2023-10-01T00:00:00Z), - a
YYYY-MM-DDdate (e.g.2026-03-30, interpreted as the start of the following day in UTC, so2026-03-31T00:00:00Z), or - a relative duration (e.g.
7d,1h30m,30m; anything thehumantimecrate accepts, relative to solve time).
For project-level config, set the following in your pixi.toml file:
Per-package overrides are available via the [exclude-newer] table for conda packages and [pypi-exclude-newer]
for PyPI packages. To exempt a specific package from the cooldown, set it to "0d":
For more advanced settings (e.g. per-channel overrides), the docs describe how to allow trusted internal channels or urgent fixes.
Private PyPI registries¶
If the registry does not expose upload times for a release, uv and pip will fail closed and reject to install a package
whose version would have been excluded, while poetry fails open and will allow that version to be installed.
Upload times are only supported by the JSON-version of the PyPI Simple API, so tools that only support the HTML format do not support upload times. For example, in JFrog Artifactory settings you have to enable the PyPI Simple JSON API, which is only available as of Artifactory 7.139.1 (SaaS, February 2026) or 7.146 (self-hosted, April 2026).
JavaScript Ecosystem¶
npm (JavaScript/Node.js)¶
npm added the min-release-age cooldown option in version 11.10.0. To set it globally, execute:
Or set the following in your project's .npmrc:
npm chose to use a unit that represents the number of days that a release must be
available before it will be considered for installation. In true JavaScript fashion, the other JS package managers chose
completely different units of time.
npm added min-release-age-exclude for per-package exemptions (available in npm 11.19.0+ and npm 12). The value
accepts package names or minimatch glob patterns. In your .npmrc:
min-release-age = 3
min-release-age-exclude[] = @myorg/*
min-release-age-exclude[] = my-internal-pkg
Only the named package is exempt; its transitive dependencies still follow the release-age policy unless they also
match a pattern. To temporarily disable the cooldown for an entire install (e.g. on npm releases that predate
min-release-age-exclude), pass --min-release-age=0 on the command line.
See npm documentation for more information.
pnpm (JavaScript/Node.js)¶
pnpm added cooldown support via minimumReleaseAge in version 10.16.0. The value represents the number of minutes a
release must be available before it is installed. The configuration approach differs between v10 and v11.
pnpm v11+¶
Since v11, a default cooldown of 1440 minutes (one day) is enabled out of the box. All non-authentication settings
belong in pnpm-workspace.yaml (.npmrc is reserved for authentication only). To increase the cooldown to three days:
You can exclude specific packages from the cooldown:
See pnpm documentation for more information.
pnpm v10¶
The default cooldown in v10 is 0 (disabled). To enable a three-day cooldown, add the following to your project's
.npmrc:
Alternatively, you can configure it in pnpm-workspace.yaml using the camelCase form shown in the v11 section above.
The exclude list (minimumReleaseAgeExclude) requires pnpm-workspace.yaml.
See pnpm v10 documentation for more information.
Yarn (JavaScript/Node.js)¶
Yarn added support for cooldowns via the npmMinimalAgeGate configuration option in version 4.10.0, and since
version 4.15.0 a one-day (1d) gate is applied by default. In your .yarnrc.yml file, add:
To exempt trusted packages:
More information can be found in yarn documentation.
Bun (JavaScript/Node.js)¶
Bun supports cooldowns with the minimumReleaseAge configuration option in bunfig.toml, first introduced in version
1.3. This time the value is specified in seconds:
To exempt specific packages from the cooldown:
[install]
minimumReleaseAge = 259200 # 3 days
minimumReleaseAgeExcludes = ["@types/node", "typescript"]
For more information, see bun documentation.
Deno (JavaScript/TypeScript)¶
Deno added support for cooldowns in version 2.6, and since version 2.9 a 24-hour cooldown is enabled by default
(overridden by any explicit configuration). The age can be specified as a number of minutes, an ISO 8601
duration (e.g. P3D for three days), or an RFC 3339 absolute timestamp. In your deno.json file, you can configure
it with:
Or use the --minimum-dependency-age flag:
deno install --minimum-dependency-age=P3D
deno update --minimum-dependency-age=P3D
deno outdated --minimum-dependency-age=P3D
To exempt specific packages from the cooldown, use the object form:
{
"minimumDependencyAge": {
"age": "P3D",
"exclude": ["npm:@mycompany/cli", "jsr:@mycompany/lib"]
}
}
See deno documentation for more information.
Rust Ecosystem¶
Cargo¶
Cargo doesn't have native cooldown support on stable yet. Cargo 1.94 added pubtime fields to the crate index (the
prerequisite), and an RFC (#3923)
for native cooldowns has been accepted. The implementation has landed on nightly as the unstable -Zmin-publish-age
feature (available since nightly-2026-06-21); stabilization is tracked in
#17009.
Until that is implemented, the third-party cargo-cooldown crate can be used
instead. Note that cargo-cooldown is a cargo subcommand, not a transparent wrapper. You must use
cargo cooldown <command> instead of cargo <command> for cooldowns to take effect. Setting COOLDOWN_MINUTES alone
does nothing; it is only read by the cargo-cooldown subcommand.
To exempt a specific crate from the cooldown, add an [[allow.package]] entry to cooldown.toml:
Or allow a specific crate version with [[allow.exact]]:
Ruby Ecosystem¶
Bundler¶
Bundler introduced a native cooldown feature in version 4.0.13. The cooldown value is always a non-negative integer number of days (a string, float, negative number, or array is rejected). For example, to apply a three-day cooldown to a single command:
The --cooldown flag is supported by the install, update, add, and outdated commands.
To set it for the current project, execute:
Or globally for all projects:
You can also declare a cooldown per-source directly in the Gemfile:
Or use the following environment variable:
When multiple settings apply, the override hierarchy is: command-line flag > configuration setting > per-source
Gemfile declaration. Passing --cooldown 0 disables the cooldown for all gems in that run:
There is no per-gem exemption, but you can set a per-source override in the Gemfile to exempt all gems from a
specific source (e.g. a private registry):
Bundler fails open: it only holds back versions it can prove are too new. Versions lacking a created_at timestamp
(older servers, v1-format registries, some private gems) remain resolvable. See the
RubyGems blog announcement for more
information.
Elixir Ecosystem¶
Hex¶
Hex introduced a built-in cooldown feature in version
2.5.0, merged in
pull request #1160. The cooldown config key accepts duration strings
in the form <N>d (days), <N>w (weeks), or <N>mo (months). To set a global three-day cooldown:
Or as an environment variable:
For project-level configuration in mix.exs:
To exclude specific repositories from the cooldown (useful when an organization publishes hotfixes to its own repo):
Or as an environment variable (comma-separated):
Hex fails open: releases without a published_at timestamp (legacy registry data, repos that haven't rebuilt their
index) remain resolvable. Packages already present in the lockfile bypass the cooldown entirely, and packages locked
to a version that is retired or has a security advisory are allowed to re-resolve even during cooldown.
Scala / JVM Ecosystem¶
Scala Steward¶
Scala Steward is a bot that opens dependency update
PRs for JVM projects. Despite its name, it works with multiple build tools (Maven, Mill, sbt, and Scala CLI).
It added a cooldown feature in version 0.38.0, with more detailed configuration in 0.38.1.
Cooldowns are configured per-repository in a .scala-steward.conf file at the root of the project:
Scala Steward calculates a version's age from when it first observed the version, and ignores updates
younger than minimumAge.
You can also override the cooldown for specific dependencies via dependencyOverrides:
updates.cooldown = {
minimumAge = "3 days"
}
dependencyOverrides = [
{
dependency = { groupId = "com.my-company" },
cooldown = { minimumAge = "1 day" }
},
{
dependency = { groupId = "com.example", artifactId = "foo" },
cooldown = { minimumAge = "14 days" }
}
]
The first matching entry wins, so list more specific patterns before broader ones. Note that even for internal/company-controlled libraries it's worth keeping a small cooldown (e.g. one day) rather than zero: those libraries can still pull in third-party transitive dependencies that were updated by hand and may themselves be compromised. See the Scala Steward repo-specific configuration docs for more information.
Tool Managers¶
mise¶
mise supports setting a minimum release age using a relative or absolute date. Since v2026.6.2, a default of 24h is applied.
Note: mise contains many backends to install dependencies. Not all of them support minimum-release-age. See security documentation for details.
To set a custom value, create a mise.toml with the following settings:
You can override the value for a particular package using:
It is also possible to completely disable cooldown for a package or an entire backend using minimum_release_age_excludes or by pinning a specific version.
[settings]
# Trivy and all the packages using the npm backend will have no cooldown at all
minimum_release_age_excludes = ["trivy", "npm:*"]
[tools]
node = "22.5.0" # This version will be installed regardless of the minimum_release_age setting
You can list packages using a particular backend by default using mise registry | grep ' npm:'.
Packages can also have fallback backends. This is relevant if you disable a backend. To list all packages using a
backend as fallback, use mise registry | grep -v ' npm:' | grep ' npm:'.
IDE Extensions¶
VS Code¶
VS Code can delay automatic extension updates with the extensions.autoUpdateDelay setting, introduced in version
1.125. Version 1.123 first shipped a fixed two-hour delay; 1.125 made it configurable. The value is the delay in
hours: VS Code will not auto-update an installed extension until that many hours have
passed since the new version was published to the Marketplace. The default is 2 hours, and setting it to 0 updates
extensions as soon as new versions are available. For a three-day cooldown, set it to 72 in your settings.json:
This only takes effect when extension auto-update is enabled, and it only gates updates to already-installed
extensions, not the first install of a new extension. A broader feature request
(#316867) asked for a minimumReleaseAge setting that would also
delay installing newly published extensions and versions; it was closed for the 1.125 milestone, but only the
auto-update delay above shipped, so first installs are still not gated. Until that changes, review changelogs before
installing a brand-new extension, and pin extension versions where possible.
GitHub Actions¶
GitHub Actions has no native cooldown feature, though actions referenced in workflows are dependencies like any other: in the March 2025 tj-actions/changed-files compromise, attackers re-pointed existing version tags at a malicious commit that leaked CI secrets from over 20,000 repositories. Pin actions to full commit SHAs so that a moved tag cannot change what your workflow runs, and apply a cooldown when updating those pins.
actions-up¶
actions-up is an interactive CLI that scans workflows and composite actions
and updates the actions they reference, pinning them to full commit SHAs by default. It added cooldown support via the
--min-age flag in version 1.6.0, and since version 1.16.0 a one-day cooldown is enabled by default. The value is a
number of days:
There is no per-action exemption; to bypass the cooldown for an urgent fix, run with --min-age 0 and select only the
action you need. Dependabot and Renovate can also update GitHub Actions with the cooldown settings shown in
Dependency update bots. See the
actions-up documentation for more information.
Other ecosystems¶
These language ecosystems currently offer no native cooldown support. There's an open proposal for Go, but it hasn't been accepted. NuGet, Composer, and Dart's pub also have open feature requests. Swift Package Manager doesn't have native cooldowns either, and no open request exists requesting this feature as of today. Your best bet is locking your dependencies to exact versions, and configuring cooldowns in Dependabot or Renovate for automated updates (see below).
Maven/Gradle (Java) don't have native cooldowns either, but the third-party Scala Steward bot described above can apply cooldowns to Maven projects (though it's not heavily used outside of Scala; note that Scala Steward does not officially support Gradle).
One related note: the community-run gem.coop package index, an alternative to RubyGems, is beta-testing a 48-hour delay on newly published gems at the registry level.
Dependency update bots¶
If you rely on automated dependency updates, you can configure cooldowns in their configurations as well. Renovate has
supported dependency cooldowns the longest; its minimumReleaseAge (formerly stabilityDays) has been supported for
years. Renovate 42 even made a 3-day minimum the default for npm via the config:best-practices preset.
To configure a cooldown of three days in your renovate.json file, use:
{
"packageRules": [
{
"matchUpdateTypes": [
"major",
"minor",
"patch"
],
"minimumReleaseAge": "3 days"
}
]
}
Dependabot also has a cooldown feature. Since July 2026 it applies a default three-day cooldown to version
updates even without any configuration (security updates remain exempt). You can customize it in dependabot.yml:
version: 2
updates:
- package-ecosystem: pip
directory: /
schedule:
interval: daily
cooldown:
default-days: 3
semver-major-days: 7
semver-minor-days: 3
semver-patch-days: 3
Both Renovate and Dependabot exempt security updates from cooldowns, so critical CVE fixes still get PRs immediately.
Registry-level proxies¶
Organizations that run a caching proxy such as JFrog Artifactory or Sonatype Nexus in front of public registries can enforce cooldowns at the registry level, overriding any project or CI-specific configuration. These proxies can hold newly published versions in quarantine for a configurable period before making them available for download. This works across ecosystems (npm, PyPI, Maven, and others) and ensures that even tools without native cooldown support benefit from a delay.
For self-hosted npm setups, the open-source Verdaccio registry proxy provides the same via
its bundled @verdaccio/package-filter plugin: set minAgeDays to hide any version published less than N days ago
(the plugin is disabled by default).
Container images¶
The configurations above work fine in your developer setups, but if you're building container images for development, those settings don't carry over automatically. If your team maintains shared base images for development, bake the cooldown configs into those images so that individual developers don't have to remember to configure these settings themselves.
Relative durations¶
uv, pip (26.1+), npm, pnpm, Bun, Deno, and Yarn all accept relative durations, so you can just set environment variables or add config files into the image at build time. These don't go stale because the duration is always relative to "now".
In a Containerfile:
FROM quay.io/fedora/fedora
# pip cooldown (26.1+)
ENV PIP_UPLOADED_PRIOR_TO="P3D"
# uv cooldown
ENV UV_EXCLUDE_NEWER="3 days"
# npm cooldown (if you also use Node)
COPY .npmrc /path/to/your/app/dir
Every pip install, uv sync/uv pip install, or npm install inside the container respects the cooldown with no
extra work.
Absolute timestamps¶
For older pip versions (pip < 26.1)), compute the absolute cutoff date at build time in the same RUN step
that installs your dependencies:
FROM quay.io/fedora/fedora
COPY requirements.txt .
RUN PIP_UPLOADED_PRIOR_TO=$(date -u -d '3 days ago' '+%Y-%m-%dT%H:%M:%SZ') \
pip install -r requirements.txt
The date is evaluated when the image is built, which is exactly when pip install runs. If you maintain development
containers where developers might run pip install interactively, you'll also want the cooldown to apply at runtime.
You can replicate the same shell function wrapper from the earlier section into /etc/profile.d/ so it's sourced
for all interactive shells:
cooldowns.sh¶
The cooldowns.sh script is a small helper that
configures cooldowns across multiple package managers in a single command and can verify that everything is set up
correctly. It supports pip, uv, poetry, pdm, npm, pnpm, Yarn, Bun, Deno, Cargo, and Bundler.
Setting cooldowns¶
Each set command writes a user-wide configuration for that tool. Project-level configs are not modified. The exact
location depends on the tool:
| Tool | Method | Location |
|---|---|---|
| pip | Env var export (26.1+) or shell wrapper (older) | /etc/profile.d/cooldowns.sh (or ~/.bashrc) |
| uv | Env var export | /etc/profile.d/cooldowns.sh (or ~/.bashrc) |
| poetry | poetry config setting |
~/.config/pypoetry/config.toml |
| pdm | pdm config setting (requires PDM >= 2.27.0) |
~/.config/pdm/config.toml |
| npm | .npmrc key |
~/.npmrc |
| pnpm | .npmrc key |
~/.npmrc |
| yarn | Env var export | /etc/profile.d/cooldowns.sh (or ~/.bashrc) |
| bun | bunfig.toml key |
~/.bunfig.toml |
| deno | Shell aliases | /etc/profile.d/cooldowns.sh (or ~/.bashrc) |
| cargo | Env var export (requires cargo-cooldown crate) |
/etc/profile.d/cooldowns.sh (or ~/.bashrc) |
| bundler | Env var export (requires Bundler >= 4.0.13) | /etc/profile.d/cooldowns.sh (or ~/.bashrc) |
Tools that use profile scripts write to /etc/profile.d/cooldowns.sh if the directory exists and is writable,
otherwise they fall back to ~/.bashrc.
Tools that only support project-level configuration are not covered by this script. This includes pipenv
(cool-down-period in the Pipfile), pixi (exclude-newer in pixi.toml), mise (minimum_release_age in
mise.toml), and Scala Steward (updates.cooldown.minimumAge in .scala-steward.conf). For pipenv specifically, the PIP_UPLOADED_PRIOR_TO
environment variable set by cooldowns.sh set pip is inherited by pipenv since it uses pip under the hood.
Checking cooldowns¶
The check command scans all installed package managers and reports their cooldown status:
Checking dependency cooldown configurations...
ok pip PIP_UPLOADED_PRIOR_TO='P3D' (3-day cooldown) in /etc/profile.d/cooldowns.sh
ok uv UV_EXCLUDE_NEWER="3 days" in /etc/profile.d/cooldowns.sh
ok npm min-release-age=3d in /home/user/.npmrc
MISS cargo no cooldown configured
3 configured, 0 warnings, 1 not configured
It exits non-zero if any tool is missing a cooldown or has a stale configuration, making it useful as a CI gate.
Usage in containers¶
The script can also be used in Containerfile/Dockerfile builds. Copy it into the image and run set commands
during the build:
FROM quay.io/fedora/fedora
COPY cooldowns.sh /usr/local/bin/
RUN cooldowns.sh set pip 3d && cooldowns.sh set uv 3d && cooldowns.sh set npm 3d
You can also add a check step to verify everything is configured:
Quick reference¶
| Package Manager | Cooldown support | Configuration |
|---|---|---|
| pip | Relative durations (26.1+) | PIP_UPLOADED_PRIOR_TO="P3D" / --uploaded-prior-to P3D |
| uv | Relative durations | exclude-newer = "3 days" in uv.toml / pyproject.toml |
| pipenv | Relative durations (2026.6.2+) | cool-down-period = "3d" in Pipfile |
| poetry | Relative durations | solver.min-release-age=3 in pyproject.toml |
| PDM | Relative durations (2.26.9+) | exclude-newer = "3d" in pyproject.toml |
| pixi | Relative durations (0.67.0+) | exclude-newer = "3d" in pixi.toml |
| npm | Relative durations; exclusions (11.19+) | min-release-age=3 in .npmrc |
| pnpm | Relative durations (1-day default in v11+) | minimumReleaseAge: 4320 in pnpm-workspace.yaml |
| Yarn | Relative durations (1-day default, 4.15+) | npmMinimalAgeGate: "3d" in .yarnrc.yml |
| Bun | Relative durations | minimumReleaseAge = 259200 in bunfig.toml |
| Deno | Relative durations (24h default in 2.9+) | minimumDependencyAge: "P3D" in deno.json |
| Cargo | Unstable on nightly; third-party | cargo cooldown <cmd> via cargo-cooldown crate |
| Bundler | Relative durations (4.0.13+) | bundle config set cooldown 3 / --cooldown 3 |
| Hex | Relative durations (2.5.0+) | mix hex.config cooldown 3d / HEX_COOLDOWN="3d" |
| Scala Steward | Relative durations (0.38.0+) | updates.cooldown.minimumAge = "3 days" in .scala-steward.conf |
| GitHub Actions | Third-party only (1-day default) | npx actions-up --min-age 3 via actions-up |
| Mise | Relative durations | settings.minimum_release_age = "3d" in mise.toml |
| VS Code | Not available | Pin dependencies and review updates manually |
| Go | Not available | Dependabot/Renovate only |
| Maven/Gradle | Not available | Dependabot/Renovate only |
| NuGet | Not available | Dependabot/Renovate only |
| Composer | Not available | Dependabot/Renovate only |
Bypassing cooldowns¶
When a vulnerability is disclosed and a fix is already available, you may need to pull a specific package immediately without waiting for the cooldown to expire. Most package managers provide a way to exempt individual packages or disable the cooldown for a single run. The table below summarizes the bypass mechanism for each tool:
| Package Manager | Per-package bypass | How to bypass |
|---|---|---|
| pip | No | Unset env var or override on CLI; see pip section |
| uv | Yes | exclude-newer-package = { pkg = false } in config file |
| pipenv | No | Remove cool-down-period from Pipfile or install directly with pip |
| poetry | Yes | solver.min-release-age-exclude = "pkg" or env var |
| pixi | Yes | [pypi-exclude-newer] / [exclude-newer] table, set to "0d" |
| npm | Yes (npm 12+) | min-release-age-exclude[] in .npmrc (globs supported) |
| pnpm | Yes | minimumReleaseAgeExclude list (supports globs and version pins) |
| Yarn | Yes | npmPreapprovedPackages list (supports globs) |
| Bun | Yes | minimumReleaseAgeExcludes list in bunfig.toml |
| Deno | Yes | Object form with exclude array in deno.json |
| Cargo | Yes | [[allow.package]] / [[allow.exact]] in cooldown.toml |
| Bundler | Per-run only | --cooldown 0 disables for entire run; per-source in Gemfile |
| Hex | Per-repo only | cooldown_exclude_repos exempts entire repositories |
| Scala Steward | Yes | dependencyOverrides with per-dependency cooldown.minimumAge |
| actions-up | Per-run only | --min-age 0 disables for run; select only the needed action |
| mise | Yes | Per-tool minimum_release_age or minimum_release_age_excludes; pinned versions auto-bypass |
Important: always revert bypass exemptions after installing the fix. A forgotten entry in a config file permanently weakens your cooldown protection for that package. For tools with per-package support, add the exemption, install the fix, then remove it. For tools without per-package support (pip, npm 11), temporarily override the cooldown for the entire install command and pin the version you need. Both Renovate and Dependabot exempt security updates from cooldowns by default, so CVE fix PRs still arrive immediately regardless of your cooldown configuration.
FAQ¶
How long should my cooldown be?¶
A longer cooldown catches more compromised releases, while a shorter one gets you fixes and features sooner. The (minimal) analysis above found that most supply-chain attacks are caught within a week, and a three-day cooldown would have blocked the majority of recent (2025/2026) incidents. As a rough guide:
- Aggressive (12–24 hours): covers the fast-exploitation window; many attacks are caught within hours.
- Balanced (3 days): the default several tools now ship (Dependabot, and Renovate's npm best-practices preset).
- Conservative (7 days): catches nearly all historical incidents, at the cost of slower updates.
Several package managers now enable a cooldown by default: pnpm (1 day since v11), Deno (24 hours since 2.9), and Yarn (1 day since 4.15). Pick a number you're comfortable with and apply it consistently; even one day makes a real difference.
Would cooldowns still work if everyone adopted them? Am I offloading my risks onto others?¶
Security vendors and researchers continuously scan newly published packages using automated static and dynamic analysis tools. They are not using cooldowns themselves; their entire purpose is to catch malicious releases as early as possible. When they flag a package, the registry pulls it, typically within hours. So even in a world where every developer uses cooldowns, malicious packages would still be detected and removed well before the cooldown window expires. This also means that adopting cooldowns does not merely offload your risks onto others, but relies on the work of security vendors and researchers to catch malicious packages first.
Don't cooldowns block security fixes?¶
Cooldowns are one part of a secure supply chain, not the whole thing. The other part is active vulnerability scanning and dependency update bots. Both Dependabot and Renovate exempt security updates from cooldowns automatically, so PRs for critical CVE fixes still arrive immediately.
Native package-manager cooldowns can't tell a security fix from any other new release, they only filter by age. A CVE fix published inside the cooldown window is held back too. You can override behavior manually: exempt the package where your tool supports it (uv, poetry, npm, pnpm, Yarn, Bun, Deno, Cargo, and others), or override the whole install where it doesn't (notably pip). See Bypassing cooldowns for per-tool syntax.
Should cooldowns replace lockfiles?¶
No. If your dependencies are locked (via package-lock.json, uv.lock, poetry.lock, etc.) and you only update
the lockfile deliberately, you're already protected most of the time. You won't pull in a newly published malicious
version unless you explicitly run an update. Cooldowns and lockfiles solve different problems. Lockfiles ensure
reproducible installs; cooldowns protect the moment when you do resolve new versions. If you use automated
dependency update bots like Renovate or Dependabot to keep your lockfile current, configure cooldowns in those tools
so that new versions still go through a waiting period before a PR is opened.
Conclusion¶
It is worth noting that cooldowns don't protect against typosquatting, long-term maintainer compromise, or zero-day vulnerabilities in packages you already have installed. Attacks like the xz-utils compromise, where a trusted maintainer introduced a backdoor over the course of months, are fundamentally different from the compromises that cooldowns target. Those attacks are also rare and require a much higher level of sophistication; they are better addressed by code review, reproducible builds, and distribution-level auditing.
A cooldown can also delay legitimate security patches, so pair cooldowns with active vulnerability alerting
(pip-audit, npm audit, Dependabot security updates) to make sure critical fixes still reach you quickly. And
cooldowns only work if the malicious release is caught during the waiting period: a patient attacker can try to
outlast one by delaying the payload so it activates only after the cooldown expires, though that only gives
scanners and researchers more time to catch them first.
That said, most real-world package compromises follow the same pattern: an attacker publishes a malicious version, and it gets caught and pulled within hours or days. A three-day cooldown would have blocked the majority of recent incidents with zero ongoing effort after initial setup. Pick a number, configure it, and stay safe out there!
Changelog¶
- 2026-08-03: Noted Dart/pub's open cooldown proposal.
- 2026-08-03: Added Verdaccio to the registry-level proxy cooldown options.
- 2026-08-03: Added PDM cooldown documentation.
- 2026-08-03: Noted Yarn's default one-day cooldown (enabled since 4.15.0).
- 2026-08-03: Noted Deno's default 24-hour cooldown (enabled since 2.9).
- 2026-08-03: Dependabot defaults to a three-day cooldown for version updates.
- 2026-08-03: Hex (Elixir) cooldown feature shipped in v2.5.0.
- 2026-07-22: Added npm 12
min-release-age-excludeper-package bypass documentation. - 2026-07-22: Added pipenv cooldown documentation.
- 2026-07-21: Added GitHub Actions cooldown documentation (
actions-up). - 2026-06-26: Added Mise documentation.
- 2026-06-19: Updated VS Code documentation for the
extensions.autoUpdateDelaysetting. - 2026-06-18: Added per-package bypass documentation.
- 2026-06-12: Added Hex (Elixir) cooldown documentation.
- 2026-06-03: Added Bundler (RubyGems) cooldown documentation.
- 2026-06-01: Added VS Code documentation.
- 2026-05-27: Added Scala Steward cooldown documentation.
- 2026-05-26: Added pixi documentation.
- 2026-05-21: Added poetry configuration documentation and a note on private PyPI registries.
- 2026-05-08: Documented pip 26.1+ duration format support.