The September and November 2025 npm supply-chain waves measurably moved the conversation in adjacent ecosystems about a defender pattern that had been discussed informally for years: the cooldown. ruby/rubygems Discussion #9113 in early 2026 opened a community evaluation of whether bundle update and bundle outdated should ship a built-in cooldown option that refuses to upgrade to a version younger than a configurable threshold. The discussion is part of a broader cross-ecosystem realization that the empirical registry-side response time for malicious-package takedown sits in the 2.5-to-12 hour range, and consumers who upgrade during that window are exposed regardless of every other control. This post walks through the cooldown design from a defender perspective, what the RubyGems community is weighing, and how to implement the pattern today across multiple ecosystems while the upstream debate continues.
What is a cooldown and why is RubyGems considering it?
A cooldown, in the registry-consumer sense, is a policy that refuses to install or upgrade to a package version that was published more recently than a defined threshold (typically 24 to 72 hours). The defender intuition is straightforward: malicious packages tend to be detected and removed within hours of publication, so a consumer who waits a defined cooldown window past publish before consuming a new version benefits from the collective detection work of every other consumer and every security vendor scanning the registry. The Discussion #9113 thread on ruby/rubygems and the corresponding dev.to write-up by hsbt frame the trade-off clearly: cooldown delays legitimate upgrades by the cooldown window, but during the window, malicious versions are far more likely to have been quarantined upstream. For most production teams, a 24-72 hour delay on dependency upgrades is an acceptable cost; for high-velocity teams or teams shipping security patches, the gate needs an explicit override mechanism.
How does the proposed bundle cooldown actually work?
The proposed Bundler implementation, as discussed in #9113, would add a --cooldown flag (and corresponding Gemfile configuration) to bundle update and bundle outdated. With cooldown set to N days, Bundler would refuse to resolve to any version whose published_at timestamp on RubyGems.org is younger than N days, falling back to the most recent version that meets the cooldown threshold. The design preserves explicit-version pinning: a developer who needs a specific just-published version can override the cooldown for that gem explicitly. The same pattern works for security-patch flows: a --security-priority mode would allow consumption inside the cooldown window if the new version is itself a security release. The discussion is ongoing as of mid-2026, but the design intent is mainstream defender pattern dressed up for Bundler's specific workflow.
What signals does the cooldown rely on?
Three signals make cooldown work. The first is the published_at timestamp on every version, which every major registry publishes through its public API. The second is the malicious-package and quarantine feed, which gives the cooldown its empirical basis: a cooldown is only valuable if the registry actually catches malicious versions inside the window. The third is the security-classification signal on the new version, which lets the cooldown distinguish "routine version bump" from "security patch the consumer needs to take immediately." The RubyGems Discussion #9113 explicitly contemplates how to make the security-patch path work without burdening every release with a manual classification, which is the harder design problem.
How do you implement cooldown across ecosystems today?
Several tools already support cooldown-style policy under different names. Renovate's minimumReleaseAge and Dependabot's cooldown configuration both implement the pattern as part of dependency-update PR automation. For tools without native support, the pattern can be implemented by filtering the candidate-version set during resolution.
# renovate.json example
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:base"],
"minimumReleaseAge": "72 hours",
"packageRules": [
{
"matchManagers": ["bundler", "npm", "pip_requirements"],
"minimumReleaseAge": "72 hours"
},
{
"matchManagers": ["bundler"],
"matchPackagePatterns": ["^rails$"],
"matchUpdateTypes": ["patch"],
"minimumReleaseAge": "24 hours"
}
]
}
# A simple shell check before running bundle update
ruby tools/check_versions_cooldown.rb \
--gemfile-lock Gemfile.lock \
--candidate-set $(bundle outdated --strict) \
--cooldown-hours 72
For npm consumers, the same pattern works through Renovate, Dependabot, or a homegrown wrapper around npm view <pkg> time. The PyPI JSON API exposes the same upload_time field, and crates.io's API exposes equivalent metadata. The cooldown pattern is genuinely cross-ecosystem; the RubyGems-side conversation is making it a first-class registry-aware concept rather than a tool-side workaround.
What policy gate catches the malicious-publish window going forward?
Three gates close the residual install-window risk. Gate one is "no upgrade of any production dependency to a version younger than N hours, with N tuned to your empirical risk tolerance," typically 24-72 hours. Gate two is "explicit override required for cooldown bypass, with the override logged for audit," which preserves the speed path for security patches while keeping accountability. Gate three is "cooldown threshold scales with package tier," with longer windows for tier-one critical dependencies and shorter ones for less-attacked leaf packages. The pnpm "How We're Protecting Our Newsroom from npm Supply Chain Attacks" post from December 2025 describes a real-world variant of this pattern, and the Bundler discussion is the upstream-ecosystem version of the same idea.
What did the 2025 waves teach about cooldown effectiveness?
The empirical case for cooldown is now reasonably strong. The September 2025 chalk/debug compromise was removed within 2.5 hours; a 24-hour cooldown would have spared every consumer who had not yet upgraded to the malicious version. The November Shai-Hulud 2.0 wave was contained in roughly 12 hours; a 72-hour cooldown would have neutralized the consumer exposure across most affected packages. The TanStack mini-Shai-Hulud incident showed a similar timing profile. The trade-off is real: organizations shipping security patches inside the cooldown window had to override explicitly, and the override path needs to be smooth enough to use under pressure. The defender argument the RubyGems Discussion is making is that the cost of the cooldown is bounded and explicit, while the cost of consuming during the malicious-publish window is open-ended.
What still has to mature?
Two gaps stand out. The first is the security-patch classification: cooldown is only as good as the consumer's ability to distinguish "routine version bump" from "security release that should bypass cooldown." The OSV and GitHub Advisory Database carry vulnerability metadata, but matching them to specific version bumps requires careful tooling. The second is registry-side support. Today, cooldown is enforced at the consumer side, which means each organization implements it independently with varying maturity. A registry-native cooldown mode, where the registry refuses to serve a version younger than a configurable threshold to consumers who opt in, would centralize the policy and make it easier for the long tail of consumers to benefit. The RubyGems Discussion is the closest the ecosystem has gotten to that idea.
How Safeguard Helps
Safeguard implements cooldown as a first-class policy gate across every package ecosystem it supports: npm, PyPI, RubyGems, crates.io, NuGet, Maven Central, and the major container and AI-model registries. Per-tier policies can set different cooldown windows for tier-one and tier-two dependencies, with explicit override workflows for security patches and audit logging for every override decision. The provenance verification engine combines cooldown with attestation requirements, so an organization can write a policy like "no upgrade to a version younger than 72 hours unless it carries a valid Trusted Publishing attestation from the publisher's known workflow," reducing the cooldown for trusted provenance signals. The malicious-package feed integration tells you when a cooldown window successfully blocked an install that turned out to be malicious, giving defenders an empirical basis for tuning the window over time. The result is that the cooldown pattern the RubyGems community is debating becomes available to every Safeguard tenant today, with the registry-native version graceful to fold in once it ships upstream.