---
title: "Configure better cooldown policies for open source packages"
description: "Securing polyglot environments is tough when npm, pnpm, Bun, and pip handle time differently. Learn how to fix package manager configuration drift and unify supply chain security with registry-side cooldown policies."
canonical_url: "https://cloudsmith.com/blog/how-to-configure-time-for-cooldown-policies"
last_updated: "2026-07-17T13:17:27.932Z"
---
# Configure better cooldown policies for open source packages

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](https://cloudsmith.com/blog/your-upstream-is-not-your-friend-why-security-teams-are-demanding-package-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**](https://github.com/jdx/mise/discussions/9042).

```json
{
  "_key": "0bf6ed39a98a",
  "_type": "code",
  "code": "npm config set min-release-age=2",
  "filename": null,
  "language": "shell",
  "markDefs": null
}
```



`npm` 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**.

```json
{
  "_key": "90a38e574f5f",
  "_type": "code",
  "code": "minimumReleaseAge: 2880",
  "filename": null,
  "language": "shell",
  "markDefs": null
}
```

- 1440 minutes (one day)
- 2880 minutes (2 days)

The `pnpr` registry also introduced the cooldown field `minimumReleaseAge` relatively recently, in [**v.10.16.0**](https://pnpm.io/settings#minimumreleaseage).  


**Bun** ([bunfig.toml](https://bun.com/docs/pm/cli/install#minimum-release-age)) represents the configured cooldown period in **seconds**, not minutes:

```json
{
  "_key": "b6fc83751ec1",
  "_type": "code",
  "code": "minimumReleaseAge = 259200",
  "filename": null,
  "language": "shell",
  "markDefs": null
}
```

- 86400 seconds (one day)
- 172800 seconds (2 days)



**Yarn** ([.yarnrc.yml](https://yarnpkg.com/configuration/yarnrc#npmMinimalAgeGate)) supports days and weeks in a more humanly-readable approach, similar to `npm`:

```json
{
  "_key": "1f9dbccce2cd",
  "_type": "code",
  "code": "npmMinimalAgeGate: \"3d\"\nnpmMinimalAgeGate: \"1w\"",
  "filename": null,
  "language": "shell",
  "markDefs": null
}
```

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**](https://github.com/astral-sh/uv/issues/14992). It uses relative durations, but also supports a bunch of other duration formats (inside [~/.config/uv/uv.toml](https://docs.astral.sh/uv/concepts/indexes/#configuring-exclude-newer-for-an-index))

```json
{
  "_key": "7731f2f74a05",
  "_type": "code",
  "code": "uv pip install --exclude-newer '2 days'",
  "filename": null,
  "language": "shell",
  "markDefs": null
}
```



As of April 2026, `pip` [**v.26.1**](https://pip.pypa.io/en/stable/news/#features:~:text=Allow-,%2D%2Duploaded%2Dprior%2Dto,-to%20accept%20a) provides an `--uploaded-prior-to` field that represents time in [**ISO 8601**](https://docs.digi.com/resources/documentation/digidocs/90001488-13/reference/r_iso_8601_duration_format.htm) duration format, where “**P**” is the **Period**, and “**D**” is the number of **Days**.

```json
{
  "_key": "d31f825c9d3b",
  "_type": "code",
  "code": "pip install --uploaded-prior-to P3D",
  "filename": null,
  "language": "shell",
  "markDefs": null
}
```

The 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**](https://github.com/golang/go/issues/76485) and [**NuGet**](https://github.com/NuGet/Home/issues/14657) _<u>do not</u>_ 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](https://cloudsmith.com/changelog/cooldown-policies-now-prevent-builds-from-seeing-non-compliant-packages)
- [Cooldown policies now support Go](https://cloudsmith.com/changelog/cooldown-policies-now-support-go)
- [Cooldown policies now support NuGet](https://cloudsmith.com/changelog/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.

```json
{
  "_key": "1a5fe04e55c1",
  "_type": "code",
  "code": "package cloudsmith\nimport rego.v1\ndefault match := false\n\n# Cooldown window in days. Packages whose upstream publish date is more recent than this are filtered out.\nwithin_past_days := 14\n\n# A cooldown policy is currently restricted to Go, npm, NuGet, and Python\nsupported_formats := {\"npm\", \"python\", \"go\", \"nuget\"}\n\n# Repo allow-list, by repo slug, scoped to this policy's org.\n# Empty set = policy applies to every repo in the org.\n# If repo slug is in 'included_repositories' and 'excluded_repositories', repo is excluded.\nincluded_repositories := {}\n\n# Repo deny-list, by repo slug, scoped to this policy's org.\n# Repos listed here always bypass the policy, regardless of `included_repositories`.\nexcluded_repositories := {}\ninclude_local_packages := true\n\nmatch if count(reason) != 0\n# Whether to evaluate this package at all. Skips local packages unless `include_local_packages` is enabled.\n_should_evaluate if {\n    not input.v0.package.is_local\n}\n_should_evaluate if {\n    include_local_packages == true\n}\n_repo_allowed if {\n    count(included_repositories) == 0\n    not input.v0.repository.slug in excluded_repositories\n}\n_repo_allowed if {\n    input.v0.repository.slug in included_repositories\n    not input.v0.repository.slug in excluded_repositories\n}\n# Produce a reason when the package is in scope, and was published within the cooldown window.\nreason contains msg if {\n    _should_evaluate\n    _repo_allowed\n    pkg := input.v0.package\n    within_past_days_date := time.add_date(time.now_ns(), 0, 0, 0 - within_past_days)\n    publish_date := time.parse_rfc3339_ns(pkg.upstream_metadata.published_at)\n    publish_date >= within_past_days_date\n    pkg.format in supported_formats\n    msg := sprintf(\n        \"Package %v/%v (%v) is within the %v-day cooldown period: published %v\",\n        [pkg.name, pkg.version, pkg.format, within_past_days, pkg.upstream_metadata.published_at],\n    )\n}",
  "filename": null,
  "language": "rego",
  "markDefs": null
}
```

  
If you’re interested in learning more about Cloudsmith [**cooldown policies**](https://cloudsmith.com/blog/how-cloudsmith-cooldown-policies-block-newly-published-packages-without-disrupting-your-builds), check out our official, [**up-to-date docs**](https://docs.cloudsmith.com/supply-chain-security/epm/cooldown-policy) for more info. Or watch our wonderful [**product demo**](https://www.youtube.com/watch?v=Lq0yK0xhJEs) on YouTube.
