Dependency Cooldowns¶
Compromised packages are unfortunately a regular occurrence in today's world. If attackers manage to steal a publish token or take over a maintainer account, they can push a malicious version of a package to a package registry, and anyone who installs it during the next few hours may end up compromising their system. Some recent examples:
- August 2026, npm: the ChainDrop worm hijacked
keyv(about 150 million weekly downloads) and used publish tokens stolen from its first victims to spread to 444 packages and over 2,200 malicious versions in a single morning. npm started unpublishing about an hour after the first poisoned release, but the worm kept propagating for roughly four hours. - August 2026, crates.io:
arrayref (245 million downloads) got a
new version whose build script downloaded and ran malware at compile time, so
cargo buildalone was enough to get infected. It was removed 86 minutes after publication. - March 2026, PyPI and npm: LiteLLM harvested cloud credentials and SSH keys for about two hours, the Telnyx Python SDK shipped backdoors triggered at import time, and axios dropped a remote access trojan via an injected dependency, live for 2–3 hours before npm pulled it.
Anyone who ran pip install, npm install, or cargo build 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.
Other major vendors also support this approach. GitHub also made a three-day cooldown the Dependabot default in July 2026, explaining that it gives detection signals time to surface before a malicious release reaches your project. Palo Alto Networks' Unit 42 recommends blocking any version published within the last 24 to 72 hours, since most malicious packages are identified and removed within that window. And Semgrep rolled out a one-week cooldown across its whole organization, reporting "a surprisingly large security benefit with very little developer friction".
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. Each entry takes the same value types as
exclude-newer (an RFC 3339 timestamp, a friendly duration, or an ISO 8601 duration) or false, which exempts the
package from the cooldown entirely. To exempt a specific package (e.g. to pull an urgent security fix), set it to
false in your pyproject.toml or uv.toml:
The same override can be passed on the command line (once per package), which is the better choice for a one-off fix because nothing is left behind in a config file:
There is no environment variable equivalent. Note that a timestamp override (e.g. django = "2026-08-05T00:00:00Z")
is a fixed cutoff, not a rolling window: once the global cooldown has caught up, it keeps blocking every later release
of that package. Remove overrides once the fix is installed.
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.
PDM 2.29.1 added per-package overrides in the [tool.pdm.resolution.exclude-newer-override] table. An override
accepts the same formats as exclude-newer, or false to exempt the package from the cooldown entirely:
[tool.pdm.resolution]
exclude-newer = "3d"
[tool.pdm.resolution.exclude-newer-override]
setuptools = false
Overrides can also be passed per-command via pdm lock --exclude-newer-override setuptools=false. PDM fails closed:
a distribution whose index entry lacks an upload-time field is treated as unavailable unless its override is
false.
conda¶
The conda package manager does not have a native cooldown feature in any released version yet, but one is on the way.
The tracking issue #15759 covers the whole ecosystem, and the core
--exclude-newer policy (#15761) was merged on 2026-08-21 for conda
26.9.0. Solver backends (conda-libmamba-solver, conda-rattler-solver) and conda-build support are still in review.
On the mamba side, mamba 2.9.0 (2026-08-07) shipped the
underlying --exclude-newer primitive in libmamba, but micromamba does not enforce it yet.
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, pip, and pdm 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 in version 11.17.0. 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:
A couple of related settings are worth knowing about. Since pnpm 12.3.0, an explicitly configured
minimumReleaseAge is strict by default: when no version of a dependency satisfies the cooldown, the install fails
instead of silently falling back to an older version. And minimumReleaseAgeIgnoreMissingTime (default true) makes
pnpm fail open for registries that don't return publish times. See the
pnpm documentation for these and other related
settings.
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"]
}
}
Since Deno 2.8, min-release-age in an .npmrc file is honored too, which is convenient when the same .npmrc is
shared between npm and Deno tooling. Unlike the Deno-native settings, the npm key only accepts a whole number of days.
See deno documentation for more information.
npm-check-updates (JavaScript/Node.js)¶
npm-check-updates (ncu) bumps the version ranges in
package.json rather than installing packages, so it needs its own cooldown to avoid pointing a range at a
freshly published version. It added a --cooldown option in version 18.2.0. The value is a number of days or, since
19.4.0, a string with a unit (7d, 12h, 30m):
Since 20.0.0, ncu picks up the cooldown from the active package manager's own configuration (min-release-age for
npm, minimumReleaseAge for pnpm, npmMinimalAgeGate for Yarn), so a package manager cooldown covers it with no
extra flags. Since 22.0.0, a package whose latest version is inside the cooldown window falls back to the newest
version that passes it instead of being skipped. For per-package control, cooldown accepts a predicate function in
.ncurc.js (19.1.0+). See the
cooldown documentation for details.
Rust Ecosystem¶
Cargo¶
Cargo doesn't have native cooldown support on stable yet, but it is close. Cargo 1.94 added pubtime fields to the
crate index (the prerequisite), an RFC
(#3923) for native cooldowns was
accepted, and Cargo 1.98 shipped the implementation as the unstable -Zmin-publish-age feature. The stabilization
PR (#17335) was merged on 2026-08-28 and is slated for Rust 1.100,
expected on 2026-11-12.
Until then, 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. Since version 0.3.1 it uses the same configuration keys as
the upcoming native feature (the older COOLDOWN_MINUTES variable is deprecated):
cargo install cargo-cooldown
export CARGO_REGISTRY_GLOBAL_MIN_PUBLISH_AGE="3 days"
cargo cooldown build
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,
Julia's Pkg, and
Dart's pub also have open feature requests. Composer is the furthest
along: an implementation (#12692) adding a config.policy.cooldown
setting is under review for the 2.11 milestone.
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).
For all of these, the security scanners and registry-level proxies described below can enforce a cooldown externally.
Homebrew applies a one-day cooldown to npm and pip packages inside its own formula builds (#21919, merged April 2026), but a user-facing setting was declined. The maintainers argue that human review of every formula update already provides the delay that language-ecosystem cooldowns try to recreate, and that a blanket cooldown would slow down critical fixes for everyone. See Homebrew's supply chain security page for the reasoning.
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"
}
]
}
Renovate can only age a release that has a timestamp. Since Renovate 42,
minimumReleaseAgeBehaviour defaults
to timestamp-required: a release without a timestamp is treated as not yet old enough, and its update is held back
indefinitely. This matters behind artifact proxies that strip release timestamps and for registries that never return
them (container images on GHCR, Quay, or ECR, for example). Set "minimumReleaseAgeBehaviour": "timestamp-optional"
to raise such updates without an age check; Renovate then logs a warning for each one. Renovate 41 defaults to the
fail-open behaviour, with timestamp-required available as an opt-in since 41.150.0.
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
exclude:
- internal-*
The optional include and exclude lists (up to 150 entries each, * wildcards supported) restrict which
dependencies the cooldown applies to. exclude always wins over include, so a dependency listed in both is updated
immediately. See the
Dependabot options reference
for details.
Both Renovate and Dependabot exempt security updates from cooldowns, so critical CVE fixes still get PRs immediately.
Security scanners and PR checks¶
Some security products enforce a cooldown at review time rather than at install time. That makes them a second line of defense in CI, and the only option for ecosystems without a native setting.
- Socket raises a
recentlyPublishedalert for any package version whose publish date falls inside a configurable window. The "Recently Published Alert Threshold" (0 to 365 days, default 0, meaning off) is set once under Settings → Alerts → Scans and applies org-wide to every ecosystem Socket scans: npm, PyPI, Maven, Cargo, RubyGems, NuGet, Go, Conda, and OpenVSX. - StepSecurity offers a "Package Cooldown" GitHub check that fails a pull request introducing or updating a dependency published within the last N days (default 2). It covers npm, PyPI, Maven, and NuGet, and the window is configured per organization or repository.
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.
Cloudsmith takes a different approach from
quarantine: its cooldown policy filters at the index level, hiding versions younger than within_past_days from the
package index entirely, so package managers resolve to an older version instead of failing a download. Policies are
written in Rego, scoped with included_repositories and excluded_repositories, and cover Cargo, Conda, Docker, Go,
Maven, npm, NuGet, and Python.
AWS CodeArtifact has no cooldown setting, but AWS
documents a CI pattern for
gating on version age. CodeArtifact preserves the upstream publish timestamp when it caches a package (the npm
packument time field, the Maven Last-Modified header, NuGet's catalogEntry.published, the crates.io V1 API
created_at, and PyPI's upload-time), so a pipeline step can read it and fail the build for anything younger than
the cutoff. Because the npm and PyPI timestamps come through in their standard formats, pnpm, Yarn, Renovate, and uv
apply their own cooldowns through CodeArtifact without changes. Do not gate on the publishedTime field returned by
describe-package-version: it reflects ingestion time and silently falls back to the record's last-updated time.
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.17+) | 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 |
| npm-check-updates | Relative durations (18.2.0+) | ncu --cooldown 3; reads npm/pnpm/Yarn cooldown config (20.0.0+) |
| 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 or --exclude-newer-package pkg=false |
| pipenv | No | Remove cool-down-period from Pipfile or install directly with pip |
| poetry | Yes | solver.min-release-age-exclude = "pkg" or env var |
| PDM | Yes (2.29.1+) | [tool.pdm.resolution.exclude-newer-override] table, set to false |
| pixi | Yes | [pypi-exclude-newer] / [exclude-newer] table, set to "0d" |
| npm | Yes (11.17+) | 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 |
| npm-check-updates | Yes | cooldown predicate function in .ncurc.js (19.1.0+) |
| 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.17), 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¶
Show all entries
- 2026-09-14: Added AWS CodeArtifact's age-gating pattern and a note on Homebrew's internal cooldown.
- 2026-09-14: Added Socket and StepSecurity PR-time cooldown checks and Cloudsmith's index-level cooldown policy.
- 2026-09-14: Added npm-check-updates
--cooldowndocumentation. - 2026-09-14: Documented Dependabot's cooldown
include/excludelists. - 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.