CooldownΒΆ

mpm can refuse to install or upgrade any package version younger than a chosen release age. This is a supply-chain safeguard: malicious releases (compromised credentials, dependency confusion, account takeover) are typically detected and pulled from registries within days of publication, so a short waiting period keeps the most recent and most likely compromised versions off the system.

Recent examples include the XZ Utils backdoor and recurring vulnerabilities in the VS Code extension marketplace. A delay of even a few days would have given the community time to react.

Quick startΒΆ

The cooldown applies to every install and upgrade mpm performs:

$ mpm --cooldown "7 days" upgrade --all
$ mpm --cooldown "1 week" install some-package
$ mpm --cooldown 12h --allow-unsupported-managers upgrade --all   # let unsupported managers run too

--cooldown accepts three input shapes:

  • Friendly duration: 7 days, 1 week, 12h, 30m, 45s, a bare number of days (7), or 0 / empty to disable the gate.

  • ISO 8601 duration: P7D, PT12H, P1WT6H. Case-insensitive.

  • RFC 3339 absolute timestamp: 2024-05-01T00:00:00Z or with an offset like +02:00. Converted at parse time to now - timestamp; a timestamp in the future disables the gate.

The same value is settable as the cooldown key in any mpm configuration file (see Configuration for the full schema) or as the MPM_COOLDOWN environment variable.

Note

Durations resolve to a fixed number of seconds, assuming a day is 24 hours. The local time zone, DST transitions, and calendar boundaries are ignored. Calendar units (months, years) are rejected for the same reason: 28-31 days and 365-366 days make them unsuitable for a precise release-age cutoff. Use days or weeks instead.

How it worksΒΆ

When cooldown is set, mpm:

  1. Computes a UTC cutoff timestamp equal to now - cooldown.

  2. For each manager that natively enforces a release-age gate (see the support table below), injects the manager’s dedicated environment variable carrying that cutoff into every CLI call. The manager’s own resolver then excludes every version published after the cutoff, including transitive dependencies.

  3. For each manager without a native gate, skips install / upgrade with a warning (fail-closed). Pass --allow-unsupported-managers (or set require_cooldown_support = false in the config file) to run those managers anyway, without the safeguard.

  4. Leaves read-only operations (outdated, installed, search) untouched: information is never blocked, only mutations are.

The choice to delegate to each manager’s own resolver rather than reimplement the gate inside mpm is deliberate: only the resolver can apply the cutoff to the whole dependency closure (see Limitations below).

Supported managersΒΆ

The table below is the source of truth for which managers mpm can gate today and the state of the upstream effort everywhere else. Statuses:

  • βœ… Enforced: mpm actively injects a cooldown environment variable on every CLI call. Listed in the cooldown_env_var framework.

  • 🟑 Shipped upstream: the manager ships a release-age gate but mpm does not (yet) plug into it.

  • 🚧 Proposed: an open pull request, RFC, or issue is on file upstream.

  • ❌ None: no public proposal found.

  • βž– N/A: the concept does not apply (distro-curated repositories with their own staging, archived projects, meta-upgraders, …). A structural equivalent is noted when relevant.

mpm id

Status

Mechanism

Reference

apk

❌ None

β€”

β€”

apm

βž– N/A (archived June 2022)

β€”

atom/apm

apt

βž– N/A (Debian’s unstable β†’ testing β†’ stable migration is functionally similar)

β€”

Nesbitt, Package managers need to cool down

apt-mint

βž– N/A (follows apt)

β€”

β€”

brew

🚧 Proposed (closed as not planned for users; merged for internal bottle resource resolution)

(internal) --min-release-age=1, --uploaded-prior-to

Homebrew/brew#21129

cargo

🚧 Proposed (RFC 3923 merged, nightly implementation)

-Zmin-publish-age

rust-lang/cargo#17009

cask

🚧 Same as brew (inherits)

β€”

Homebrew/brew#21129

choco

❌ None

β€”

β€”

composer

🚧 Proposed

open PR adds cooldown

composer/composer#12692

cpan

❌ None

β€”

β€”

deb-get

❌ None

β€”

β€”

dnf

❌ None (effort focused on dnf5)

β€”

β€”

dnf5

🚧 Proposed

minimum_package_age (open issue)

rpm-software-management/dnf5#2743

emerge

❌ None

β€”

β€”

eopkg

❌ None

β€”

β€”

flatpak

❌ None

β€”

β€”

fwupd

βž– N/A (LVFS staged deployment)

β€”

LVFS news

gem

🚧 Proposed (Bundler PR open)

--cooldown / BUNDLE_COOLDOWN / per-source cooldown:

ruby/rubygems#9576

guix

❌ None

β€”

β€”

macports

❌ None

β€”

β€”

mas

❌ None

β€”

β€”

nix

❌ None

β€”

β€”

npm

βœ… Enforced (npm β‰₯ 11.10)

min-release-age env npm_config_min-release-age (integer days)

npm docs

opkg

❌ None

β€”

β€”

pacaur

❌ None (Arch AUR helper)

β€”

β€”

pacman

❌ None

β€”

β€”

pacstall

❌ None

β€”

β€”

paru

❌ None (Arch AUR helper)

β€”

β€”

pip

βœ… Enforced (pip β‰₯ 26.1)

--uploaded-prior-to env PIP_UPLOADED_PRIOR_TO

pypa/pip#13674

pipx

βœ… Enforced (via pip’s env var; needs the underlying pip β‰₯ 26.1)

inherits PIP_UPLOADED_PRIOR_TO

pypa/pipx#1811

pkg

❌ None

β€”

β€”

pnpm

βœ… Enforced (pnpm β‰₯ 11.0)

minimumReleaseAge env pnpm_config_minimum_release_age (minutes)

pnpm docs

ports

❌ None (FreeBSD ports)

β€”

β€”

pwsh-gallery

❌ None

β€”

β€”

scoop

🚧 Proposed

open feature request

ScoopInstaller/Scoop#6513

sdkman

❌ None

β€”

β€”

sfsu

🚧 Inherits from scoop

β€”

β€”

snap

βž– N/A (risk channels stable/candidate/beta/edge, plus snap refresh --hold up to 90 days)

snap refresh --hold

Snap docs

steamcmd

❌ None

β€”

β€”

stew

❌ None

β€”

β€”

topgrade

βž– N/A (meta-upgrader; delegates to each underlying manager)

β€”

β€”

uv, uvx

βœ… Enforced

exclude-newer env UV_EXCLUDE_NEWER

uv docs

vscode, vscodium

🚧 Proposed

proposed enterprise policy

microsoft/vscode#316867

winget

🚧 Proposed

open feature request

microsoft/winget-cli#6178

xbps

❌ None

β€”

β€”

yarn (Classic v1)

❌ None (project in maintenance mode)

β€”

yarnpkg/yarn

yarn-berry

🟑 Shipped upstream (Berry β‰₯ 4.10) but unreachable through mpm (the yarn-berry handler does not implement install / upgrade because Yarn Berry removed global installs)

npmMinimalAgeGate

Yarn settings

yay

🟑 Shipped upstream (v13 Lua hooks, upgrade-only) but unreachable through mpm

(user-authored) UpgradeSelect Lua hook

Jguer/yay#2883

yum

βž– N/A (deprecated alias for dnf on RHEL-family)

β€”

β€”

zerobrew

❌ None

β€”

β€”

zypper

❌ None

β€”

β€”

NotesΒΆ

  • brew ships internal release-age gates inside Homebrew’s bottle resource-resolution pipeline so formulae built from upstream resources get a delay automatically. Coverage expanded with Homebrew 6.0.0 to include npm and pip (Homebrew/brew#21919), PyPI (Homebrew/brew#21920), RubyGems (Homebrew/brew#22253), and Bundler (Homebrew/brew#22555). All four gate source dependencies fetched during a formula build; none expose a user-facing knob. The issue requesting one for brew install (Homebrew/brew#21129) was closed as not planned. The scope is distinct from mpm’s --cooldown, which gates the package version mpm asks brew to install: brew’s internal gate keeps build-time dependencies fresh-but-not-too-fresh, mpm’s gate delays the package version itself. Both can be in effect simultaneously without conflict.

  • pipx delegates to whichever pip lives inside its managed virtualenvs. If that pip predates 26.1 (or pipx routes resolution through uv instead), PIP_UPLOADED_PRIOR_TO is silently ignored. mpm has no clean way to inspect pipx’s internal resolver, so treat the pipx gate as best-effort.

  • yarn-berry: the gate works in Berry β‰₯ 4.10, but Yarn Berry removed yarn global, so mpm’s yarn-berry handler only implements search. Onboarding npmMinimalAgeGate would not change anything reachable through mpm.

  • apt, snap, fwupd all have structural delays (Debian’s migration windows, Snap risk channels, LVFS staged deployment) rather than per-version age gates. They’re marked N/A because the underlying ecosystem solves the problem in a different shape.

  • yay added user-programmable Lua hooks in v13. An UpgradeSelect hook can filter the upgrade set by AUR LastModified age (yay ships an example at doc/examples/recently_modified.lua), and the maintainer closed the dedicated minimum-release-age requests (Jguer/yay#2824, Jguer/yay#2848) in favor of it. Two gaps keep it out of mpm’s reach: the hook loads only from the user’s fixed ~/.config/yay/init.lua, so mpm cannot inject a per-run policy without clobbering that file (Jguer/yay#2883), and UpgradeSelect fires only on yay -Syu, so a fresh yay -S install and its new AUR dependencies stay ungated regardless.

  • The Arch AUR helpers (pacaur, paru) and most distro front-ends inherit whatever delay the underlying repository / AUR provides; none of them ship a dedicated cooldown setting.

LimitationsΒΆ

The transitive-dependency gapΒΆ

mpm’s cooldown is exactly as good as the underlying resolver’s. For managers that install with a real dependency resolver (PyPI, npm, …), the native mechanism applies the cutoff to the whole tree, including transitive dependencies. For managers without a native mechanism, mpm cannot retrofit one without reimplementing the resolver: pinning only the top-level package would leave transitive dependencies fresh, which is precisely the most common attack vector. That is why unsupported managers are fail-closed rather than fail-open.

Coverage limitsΒΆ

Distro and system managers (apt, dnf, pacman, brew, …) generally have no per-upstream publish date attached to a package version: their version string is the distro maintainer’s package build, not the upstream release, and the threat model differs (curated repositories with their own staging and review). The concept does not cleanly map. These managers are listed in the support table as N/A.

Read-only consistencyΒΆ

The outdated report is not filtered by the cooldown on unsupported managers, so it may list versions that the subsequent upgrade would skip. For supported managers the same environment variable also affects outdated, so the report and the upgrade stay consistent.

Possible future directionsΒΆ

  • Detect pipx’s internal pip (or uv) at runtime. mpm’s pip manager has a hard >=26.1.0 floor, but pipx maintains its own virtualenvs whose pip may be older or whose resolution may be routed through uv (where the right env var is UV_EXCLUDE_NEWER instead of PIP_UPLOADED_PRIOR_TO). Probing the resolver per venv would let mpm refuse to advertise enforcement when the underlying pip is stale.

  • Onboard mechanisms as they ship upstream. Several managers have active work that would slot into the cooldown_env_var framework as a one-line addition once released: Composer (#12692), Bundler / RubyGems (#9576), Cargo (stabilization of -Zmin-publish-age, #17009), dnf5 (#2743), Scoop (#6513), winget (#6178), VS Code (#316867).

  • Advisory mode for outdated on unsupported managers. mpm could query each package registry directly (PyPI, RubyGems, crates.io, …) to annotate outdated with a β€œsafe latest” column: purely informational, no install-side enforcement. This avoids the transitive-resolution trap while still being useful. It requires a new HTTP client surface and a state directory for date caching, neither of which mpm has today.

  • Block-mode for bundled-artifact managers (snap, flatpak, vscode, mas). These install self-contained artifacts with no separate transitive resolution at install time, so a β€œrefuse if fresher than the cutoff” check would be sound without a resolver. The bottleneck is per-store API support for per-version publish dates.

Prior artΒΆ