This post was requested by Seth Larson, who asked if I could do a breakdown of dependency cooldowns across package managers. His framing: all tools should support a globally-configurable exclude-newer-than=<relative duration> like 7d, to bring the response times for autonomous exploitation back into the realm of human intervention.

When an attacker compromises a maintainer’s credentials or takes over a dormant package, they publish a malicious version and wait for automated tooling to pull it into thousands of projects before anyone notices. William Woodruff made the case for dependency cooldowns in November 2025, then followed up with a redux a month later: don’t install a package version until it’s been on the registry for some minimum period, giving the community and security vendors time to flag problems before your build pulls them in. Of the ten supply chain attacks he examined, eight had windows of opportunity under a week, so even a modest cooldown of seven days would have blocked most of them from reaching end users.

The concept goes by different names depending on the tool (cooldown, minimumReleaseAge, stabilityDays, exclude-newer) and implementations vary in whether they use rolling durations or absolute timestamps, whether they cover transitive dependencies or just direct ones, and whether security updates are exempt.

JavaScript

The JavaScript ecosystem moved on this faster than anyone else, with pnpm shipping minimumReleaseAge in version 10.16 in September 2025, covering both direct and transitive dependencies with a minimumReleaseAgeExclude list for packages you trust enough to skip. Yarn shipped npmMinimalAgeGate in version 4.10.0 the same month (also in minutes, with npmPreapprovedPackages for exemptions, and settable per npm scope since 4.17.0), then Bun added minimumReleaseAge in version 1.3 in October 2025 via bunfig.toml. npm took longer but shipped min-release-age in version 11.10.0 in February 2026, with a min-release-age-exclude exemption list following in 11.17.0. Deno has --minimum-dependency-age (aliased to --min-dep-age since 2.9.3) for deno update and deno outdated, and from 2.8 also reads min-release-age out of .npmrc. Five package managers in six months, which I can’t think of a precedent for in terms of coordinated feature adoption across competing tools.

Python

uv has had --exclude-newer for absolute timestamps since early on and added relative duration support (e.g. 1 week, 30 days) in version 0.9.17 in December 2025, along with per-package overrides via exclude-newer-package. pip shipped --uploaded-prior-to in version 26.0 in January 2026 with absolute timestamps, then added relative duration support in version 26.1. Poetry added solver.min-release-age in version 2.4.0, so all three major Python installers now have it.

Ruby

Bundler 4.0.13 shipped Cooldown on 3 June 2026, implementing maintainer Hiroshi Shibata’s design proposal: a cooldown setting in days that can be passed as a CLI flag (bundle install --cooldown 7), set via bundle config set cooldown 7 or the BUNDLE_COOLDOWN environment variable, or attached per source in the Gemfile as source "https://rubygems.org", cooldown: 7. Hanging it on the source rather than on individual gems means private registries simply don’t get a cooldown, so there’s no exclude list to maintain, and --cooldown 0 is the escape hatch when you need a CVE fix immediately. The filter applies to transitive dependencies too, and lockfile-pinned versions bypass it so existing locks don’t break. RubyGems 4.1.0.beta1 extends the same --cooldown flag to gem install, gem update, and gem outdated, so standalone gem installs outside a Bundler project are covered too.

Separately, gem.coop, a community-run gem server, supports cooldowns on every namespace: append /cooldown to the source URL and newly published gems are held back for 48 hours. The feature is staying on the beta domain while reported bugs get fixed. Pushing the cooldown to the index level rather than the client is interesting because any Bundler user pointed at a cooldown source gets the delay without changing their tooling or workflow at all.

Other ecosystems

Cargo’s min-publish-age RFC was merged in May 2026 and the registry-side infrastructure for cooldowns was stabilized in Cargo 1.94. The client side landed as the unstable -Zmin-publish-age flag in the Cargo shipped with Rust 1.98 in August 2026, so it’s usable on nightly but not yet stabilized. Their approach sidesteps the exemption list problem entirely: instead of exempting packages from cooldowns, you explicitly opt in to a new version with cargo update foo --precise 1.5.10, which records the choice in your lockfile. No exclude list to remember to clean up later. There’s also cargo-cooldown, a third-party wrapper that enforces a configurable cooldown window on developer machines as a proof-of-concept for stable Cargo users.

Go has an open proposal for go get and go mod tidy, Composer has an open issue and the config.policy schema introduced in Composer 2.10 reserves minimum-release-age as a future built-in policy name, and NuGet has an open issue though .NET projects using Dependabot already get cooldowns on the update bot side since Dependabot expanded NuGet support in July 2025. Dart’s pub has an open issue proposing minimum_release_age in pubspec.yaml with an exceptions list, and the discussion there has branched into whether a version should also be skipped when a newer one was published shortly after it, on the theory that a quick follow-up release probably fixed something you don’t want. Elixir’s Hex 2.5 shipped a cooldown setting for mix deps.get and mix deps.update, configurable via hex: [cooldown: "7d"] in mix.exs, mix hex.config cooldown 7d, or the HEX_COOLDOWN environment variable, and the cooldown is lifted automatically for a package whose currently locked version is retired or carries an advisory so you’re never held on a known-bad release waiting out the window. Conda, which manages packages for R and other languages as well as Python, has an open issue proposing --exclude-newer, and Mamba 2.9.0 added that same flag to its drop-in reimplementation of the conda CLI in August 2026.

VS Code added a two-hour delay on extension auto-updates in version 1.123 and exposed it as the extensions.autoUpdateDelay setting (in hours, default 2) in 1.125, which administrators can push fleet-wide via enterprise policies. It only applies to auto-updates: the manual Update button and fresh installs bypass it, and extensions from trusted publishers (Microsoft, GitHub, OpenAI) skip the delay. Two hours is a long way from the seven-day baseline the rest of this post works from, so if you’re setting it via policy you probably want a bigger number.

mise, the polyglot runtime and tool manager, has a global minimum_release_age setting that filters tool versions by publish date across every backend it installs from, with minimum_release_age_excludes for tools or backends you want to exempt.

Dependency update tools

Renovate has had minimumReleaseAge (originally called stabilityDays) for years, adding a “pending” status check to update branches until the configured time has passed, and 44.103.2 added a companion minimumReleaseAgeBuffer to cover sibling packages published shortly after the main package. Depfu shipped a “reasonably up-to-date” strategy in June 2019 that holds new releases for anywhere from a few days to a month before opening a PR, pitched at the time as a way to cut PR volume rather than a supply chain defence. Mend Renovate 42 made a 3-day minimum release age the default for npm packages in their “best practices” config via the security:minimumReleaseAgeNpm preset, making cooldowns opt-out rather than opt-in for their users.

Dependabot shipped cooldowns in July 2025 with a cooldown block in dependabot.yml supporting default-days and per-semver-level overrides (semver-major-days, semver-minor-days, semver-patch-days), and as of dependabot-core 0.390.0 a three-day cooldown is the unconditional default, so projects now opt out rather than in. Security updates bypass the cooldown, and Dependabot also passes minimumReleaseAge=0 through to pnpm and Yarn Berry on security PRs so a project-level cooldown config in pnpm-workspace.yaml or .yarnrc.yml doesn’t block the fix either. Snyk takes the most aggressive stance with a built-in non-configurable 21-day cooldown on automatic upgrade PRs. npm-check-updates added a --cooldown parameter that accepts duration suffixes like 7d or 12h.

Checking your config

zizmor added a dependabot-cooldown audit rule in version 1.15.0 that flags Dependabot configs missing cooldown settings or with insufficient cooldown periods (default threshold: 7 days), with auto-fix support. StepSecurity offers a GitHub PR check that fails PRs introducing npm packages released within a configurable cooldown period. OpenRewrite has an AddDependabotCooldown recipe for automatically adding cooldown sections to Dependabot config files. For GitHub Actions specifically, pinact added a --min-age flag, and prek (a Rust reimplementation of pre-commit) added --cooldown-days. If you’re wiring the check into your own tooling rather than using one of the above, git-pkgs/cooldown is a Go library that applies a release-age filter across npm, PyPI, Cargo, RubyGems, Composer, Conda, Hex, NuGet, and Pub from a single config.

Still waiting

For Go, Composer, Dart, and conda (Mamba aside), cooldown support is still in discussion, which means you’re relying on Dependabot or Renovate to enforce the delay. That covers automated updates, but nothing stops someone from running go get or composer update locally and pulling in a version that’s been on the registry for ten minutes. I couldn’t find any cooldown discussion at all for Maven, Gradle, or Swift Package Manager. If you know of one, let me know and I’ll update this post.

The feature also goes by at least fourteen different configuration names across the tools that do support it (cooldown, minimumReleaseAge, min-release-age, minimum_release_age, npmMinimalAgeGate, exclude-newer, stabilityDays, uploaded-prior-to, min-age, cooldown-days, minimum-dependency-age, min-dep-age, min-publish-age, autoUpdateDelay), which makes writing about it almost as hard as configuring it across a polyglot project.

Language vs. system package managers

On npm, PyPI, and RubyGems, running npm publish or gem push makes a package installable worldwide in seconds, and if Dependabot or Renovate happens to run in that window, the malicious code lands in a project without a human ever seeing it. All of the supply chain attacks William examined exploit this property, where publishing and distribution are the same act and nothing stands between a compromised maintainer account and thousands of downstream projects.

System package managers work differently because they separate those two things. When someone pushes a new version of an upstream library, it doesn’t appear in apt install or brew install until a distribution maintainer has reviewed the change, updated the package definition, and pushed it through a build pipeline. Fedora packages go through review and koji builds, Homebrew requires a pull request that passes CI and gets merged by a maintainer. A compromised upstream tarball still has to survive that process before it reaches anyone’s machine, and the people doing the reviews tend to notice when a patch adds an obfuscated postinstall script that curls a remote payload. On Debian, even a compromised maintainer’s upload lands in unstable first, then automigrates to testing after 2 to 10 days depending on urgency and availability of package tests, and stable only gets updates through a separate release process.

Cooldowns on the language package manager side retrofit something like that review window onto ecosystems that never had one, giving security researchers a few days to flag a malicious publish before automated tooling pulls it into lockfiles. Asking Homebrew or apt to add the same feature would mean delaying security patches through a process that already has human gatekeepers, which costs more than it saves.

The timestamp problem

pip’s --uploaded-prior-to and npm’s older --before flag originally only took absolute timestamps, and the discussion about adding relative duration support to pip reveals how these two modes serve different goals that happen to share implementation surface. An absolute timestamp pins your dependency resolution to a moment in time, so running the same install six months from now produces the same result. A relative duration like 7 days is the security version: a sliding window that moves forward with you, always excluding recent publishes regardless of when the build runs. pip added relative durations in 26.1, uv’s --exclude-newer accepts both forms, and npm has both --before for absolute dates and min-release-age for relative durations. pnpm, Yarn, Bun, and Deno only accept relative durations.

The pip thread also got into the fiddly business of parsing duration strings. ISO 8601 durations (P7D) are unambiguous but nobody wants to type them, human-readable strings like 7 days are friendly but need a parser, and variable-length calendar units like months and years require knowing which month you’re in to convert to a concrete number of days. uv went with ISO 8601 plus friendly strings but excluded months and years entirely, and pip went with relative durations in 26.1, which covers nearly every real use case without dragging in leap year arithmetic.

Even the question of what “seven days ago” means gets complicated when your CI server is in UTC, your developer laptop is in US Pacific time, and the registry timestamp uses whatever timezone PyPI’s servers happen to be configured with. A few hours of timezone skew can determine whether a package published six days and twenty-two hours ago passes the cooldown check or not.

Which timestamp counts as “published” also varies: Dependabot’s GitHub Actions cooldown uses the tag’s commit date rather than the release publication date, so a tag cut 100 days ago but only released yesterday sails straight through a 7-day cooldown. Most language registries have a single upload timestamp so this doesn’t come up, but anything sourced from git tags (GitHub Actions, Go modules, git submodules) has at least two candidate dates that can be months apart.

Whether the registry exposes a timestamp at all predicts adoption speed pretty well: the ecosystems that already had publish times in their index API moved fastest, because cooldowns become a pure client-side filter when the resolver can already see when each version landed. npm’s packument has carried a per-version time field for years and PyPI added upload-time to the simple index via PEP 700 in 2022, so pnpm, uv, and pip could ship the feature without touching the server.

The npm field does come with a cost though, since time only appears in the full packument and not the abbreviated response most installers normally request, so enabling cooldowns means pulling multi-megabyte JSON files for popular packages instead of the slim variant. RubyGems’ compact index doesn’t carry timestamps at all, so Bundler’s Cooldown reads created_at from the dependency API endpoint instead, and there’s a separate open PR to add timestamps to the compact index /info response so the resolver doesn’t need a second round-trip.