
How to configure time for cooldown policies

If time’s just a construct of human perception, does that mean all package managers interpret time differently? And what does that have to do with cooldown policies?
Cooldown policies help protect your software supply chain from zero-hour threats by holding packages for a set period of time to allow threat intelligence to catch up. But where does a package get held, and how does that impact how you configure your cooldown policy?
You can implement cooldown controls on either the client-side and on the server-side. This post highlights the difference between where the cooldown policy lives, and how that impacts your ability to implement and scale that policy. We’ll explore how they can be defined differently in various ecosystem package managers, and why all this flexibility actually might hinder (not help) the security posture of our software supply chain.
JavaScript ecosystem
JavaScript developers most commonly pull .js software packages from the npm registry. The most popular Javascript package manager, npm, included the concept of a minimum release age (min-release-age) this year, starting in v.11.10.0.
npm config set min-release-age=2npm represents the number of days a dev must wait before they can receive a newly-released software dependency, as a single number unit. However, not all JavaScript package managers work the same way. The pnpm client, for example, chose a completely different unit of time. We can see that pnpm specifically treats cooldown (within the .npmrc file) in the form of minutes.
minimumReleaseAge: 2880- 1440 minutes (one day)
- 2880 minutes (2 days)
The pnpr registry also introduced the cooldown field minimumReleaseAge relatively recently, in v.10.16.0.
Bun (bunfig.toml) represents the configured cooldown period in seconds, not minutes:
minimumReleaseAge = 259200- 86400 seconds (one day)
- 172800 seconds (2 days)
Yarn (.yarnrc.yml) supports days and weeks in a more humanly-readable approach, similar to npm:
npmMinimalAgeGate: "3d"
npmMinimalAgeGate: "1w"This leaves JavaScript teams in a situation where securing a polyglot Node.js environment requires a math degree and a cheat sheet! If your team wants to implement a cooldown policy and uses a mix of npm, pnpm, bun, and yarn (as only a few examples), you need to translate a basic 48-hour quarantine policy into days, minutes, seconds, and human-readable string tags simultaneously. This arbitrary config drift is more than a tedious task, it’s a genuine security liability. When security policies are this fragmented across tools running in the same language ecosystem, misconfigurations are more likely.
Python ecosystem
The Python ecosystem is similar to JavaScript when it comes to representations of time. Astral’s uv package manager provided cooldown controls in v.0.9.17. It uses relative durations, but also supports a bunch of other duration formats (inside ~/.config/uv/uv.toml)
uv pip install --exclude-newer '2 days'As of April 2026, pip v.26.1 provides an --uploaded-prior-to field that represents time in ISO 8601 duration format, where “P” is the Period, and “D” is the number of Days.
pip install --uploaded-prior-to P3DThe result in the Python ecosystem is a case of duplicate standards. While more modern, lightning-fast tools like uv embrace highly flexible relative text strings, standard pip leans heavily into the rigid syntax of ISO 8601. For security teams trying to enforce a consistent supply chain guardrail across a variety of developer endpoints and pipelines, this introduces a layer of translation friction. We aren't simply configuring the time window, we’re also managing entirely different syntactical philosophies from what was discussed in the JavaScript ecosystem - just to keep a volatile package at bay.
Package Managers with no support for Cooldown Policies
And that brings us to the lack of support for cooldown policies in many other popular language ecosystems. As of the time of writing, languages like Go and NuGet do not provide client-side support for cooldown controls in any direct configuration field.
In the absence of built-in cooldowns, devs without server-side or private artifact repository solutions rely on strict version pinning as their primary line of defence. But version pinning is a double-edged sword:
- Pinning to an older version should (generally speaking) keep you safe from a zero-day compromise in today's release, but it leaves you exposed to older, known CVEs.
- Pinning only works if you trust the version you are pinning. If you update your config to pin a newly released version and that version happens to be a compromised release from a hijacked maintainer account, well then, your client will happily download and index that malware regardless.
Without a cooldown policy acting as a quarantine buffer, devs updating their pinned dependencies are essentially playing Russian roulette with the latest release.
On a positive note, the open source supply chain is evolving all the time. Where cooldown controls don't exist today, they could very well exist tomorrow. What’s important to software developers and security teams is that we can curate or enforce security controls that seamlessly apply across the wide variety of languages and runtime ecosystems.
That’s where an artifact management platform like Cloudsmith comes to the rescue. The platform provides cooldown policy support for npm, Python, Go, and NuGet, with additional format support planned:
- Cloudsmith supports cooldown policies in npm and Python
- Cooldown policies now support Go
- Cooldown policies now support NuGet
Closing the gap with Cloudsmith
If time really is just a construct of human perception, package managers have certainly taken that philosophy to heart. But the real headache is trying to actually implement security controls in the real world. This is fundamentally a client-side crisis.
We are forcing devs and their pipelines to rely on local, fragmented CLI tooling to enforce security policies that should be uniform. The fragmentation here is a massive vulnerability:
- Different naming conventions and arbitrary time units across clients make universal configurations very challenging.
- Some client-side tools allow granular exclusions, while others offer no cooldown protection at all.
- While centralised registries like Cloudsmith work overtime to curate, scan, and secure upstreams, they need to account for rogue, outdated, or poorly designed local clients that bypass standard safeguards.
Trying to secure a polyglot codebase where the security posture of your entire pipeline is dictated by the weakest client-side tool in your stack is a logistical nightmare.
Cloudsmith solves this systemic headache by providing a single control plane for your entire software supply chain. By defining a universal policy that sits above individual ecosystems, you don’t waste time tweaking configurations across a wide range of runtimes. With Cloudsmith, you define the policy once, and enforce it across your entire workspace.
package cloudsmith
import rego.v1
default match := false
# Cooldown window in days. Packages whose upstream publish date is more recent than this are filtered out.
within_past_days := 14
# A cooldown policy is currently restricted to Go, npm, NuGet, and Python
supported_formats := {"npm", "python", "go", "nuget"}
# Repo allow-list, by repo slug, scoped to this policy's org.
# Empty set = policy applies to every repo in the org.
# If repo slug is in 'included_repositories' and 'excluded_repositories', repo is excluded.
included_repositories := {}
# Repo deny-list, by repo slug, scoped to this policy's org.
# Repos listed here always bypass the policy, regardless of `included_repositories`.
excluded_repositories := {}
include_local_packages := true
match if count(reason) != 0
# Whether to evaluate this package at all. Skips local packages unless `include_local_packages` is enabled.
_should_evaluate if {
not input.v0.package.is_local
}
_should_evaluate if {
include_local_packages == true
}
_repo_allowed if {
count(included_repositories) == 0
not input.v0.repository.slug in excluded_repositories
}
_repo_allowed if {
input.v0.repository.slug in included_repositories
not input.v0.repository.slug in excluded_repositories
}
# Produce a reason when the package is in scope, and was published within the cooldown window.
reason contains msg if {
_should_evaluate
_repo_allowed
pkg := input.v0.package
within_past_days_date := time.add_date(time.now_ns(), 0, 0, 0 - within_past_days)
publish_date := time.parse_rfc3339_ns(pkg.upstream_metadata.published_at)
publish_date >= within_past_days_date
pkg.format in supported_formats
msg := sprintf(
"Package %v/%v (%v) is within the %v-day cooldown period: published %v",
[pkg.name, pkg.version, pkg.format, within_past_days, pkg.upstream_metadata.published_at],
)
}
If you’re interested in learning more about Cloudsmith cooldown policies, check out our official, up-to-date docs for more info. Or watch our wonderful product demo on YouTube.
More articles


How to use Cloudsmith as a dependency firewall

A dependency firewall gives your scanner better inputs

Dependency attacks start before your scanner runs

Cloudsmith credentials are now detectable by GitHub Secret Scanning

