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
If you are unsure which value to pick, --cooldown '7 days' is a reasonable default: long enough for most compromised releases to be flagged and pulled from registries, short enough to keep security updates timely. There is no bare --cooldown shorthand: the option always takes an explicit duration.
--cooldown accepts three input shapes:
Friendly duration:
7 days,1 week,12h,30m,45s, a bare number of days (7), or0/ empty to disable the gate.ISO 8601 duration:
P7D,PT12H,P1WT6H. Case-insensitive.RFC 3339 absolute timestamp:
2024-05-01T00:00:00Zor with an offset like+02:00. Converted at parse time tonow - 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:
Computes a UTC cutoff timestamp equal to
now - cooldown.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.
For each manager without a native gate, skips install / upgrade with a warning (fail-closed). Pass
--allow-unsupported-managers(or setrequire_cooldown_support = falsein the config file) to run those managers anyway, without the safeguard.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:
mpmactively injects a cooldown environment variable on every CLI call. Listed in thecooldown_env_varframework.π‘ Shipped upstream: the manager ships a release-age gate but
mpmdoes 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.
|
Status |
Mechanism |
Reference |
|---|---|---|---|
β None |
β |
β |
|
β N/A (archived June 2022) |
β |
||
β N/A (Debianβs |
β |
||
β N/A (unmaintained since 2017; wraps Cygwinβs |
β |
||
β N/A (follows |
β |
β |
|
β None (runtime version manager) |
β |
β |
|
π§ Proposed (closed as not planned for users; merged for internal bottle resource resolution) |
(internal) |
||
π§ Proposed (RFC 3923 merged, nightly implementation) |
|
||
π§ Same as |
β |
||
β None (Exherbo, source-based) |
β |
β |
|
β None |
β |
β |
|
β None (Chrome OS) |
β |
β |
|
π§ Proposed |
open PR adds |
||
π§ Proposed |
|
||
β None |
β |
β |
|
β None |
β |
β |
|
β None (effort focused on |
β |
β |
|
π§ Proposed |
|
||
β None |
β |
β |
|
β None |
β |
β |
|
β None (macOS, Debian/dpkg-based) |
β |
β |
|
β None |
β |
β |
|
β N/A (LVFS staged deployment) |
β |
||
π§ Proposed (Bundler PR open) |
|
||
β None (installs extensions from git repos) |
β |
β |
|
β None |
β |
β |
|
β None |
β |
β |
|
β None |
β |
β |
|
π‘ Shipped upstream |
|
||
β None |
β |
β |
|
β Enforced (npm β₯ 11.10) |
|
||
β None |
β |
β |
|
β None (Arch AUR helper) |
β |
β |
|
β None |
β |
β |
|
β None |
β |
β |
|
β None (Arch AUR helper) |
β |
β |
|
β Enforced (pip β₯ 26.1) |
|
||
β Enforced (via pipβs env var; needs the underlying pip β₯ 26.1) |
inherits |
||
β N/A (delegates to the distro backend) |
β |
β |
|
β None |
β |
β |
|
β None (OpenBSD, per-release curated) |
β |
β |
|
β None (pkgsrc binary packages) |
β |
β |
|
β Enforced (pnpm β₯ 11.0) |
|
||
β None (FreeBSD ports) |
β |
β |
|
β None |
β |
β |
|
π§ Proposed |
open feature request |
||
β None |
β |
β |
|
π§ Inherits from |
β |
β |
|
β None (Slackware) |
β |
β |
|
β N/A (risk channels |
|
||
β None (static-binary installer) |
β |
β |
|
β None (Source Mage, source-based) |
β |
β |
|
β None |
β |
β |
|
β None |
β |
β |
|
β N/A (legacy SVR4 tooling superseded by IPS) |
β |
||
β N/A (Clear Linux discontinued July 2025) |
β |
||
β None (SliTaz) |
β |
β |
|
β None (TeX Live, curated) |
β |
β |
|
β N/A (meta-upgrader; delegates to each underlying manager) |
β |
β |
|
β None (Mageia) |
β |
β |
|
β Enforced |
|
||
β N/A (unmaintained since November 2025) |
β |
||
π§ Proposed |
proposed enterprise policy |
||
π§ Proposed |
open feature request |
||
β None |
β |
β |
|
|
β None (project in maintenance mode) |
β |
|
π‘ Shipped upstream (Berry β₯ 4.10) but unreachable through |
|
||
β Enforced (yay β₯ 13.0) |
generated |
||
β N/A (deprecated alias for |
β |
β |
|
β None |
β |
β |
|
β None |
β |
β |
NotesΒΆ
brewships internal release-age gates inside Homebrewβs bottle resource-resolution pipeline so formulae built from upstream resources get a delay automatically. Coverage expanded with Homebrew6.0.0to 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 forbrew install(Homebrew/brew#21129) was closed as not planned. The scope is distinct frommpmβs--cooldown, which gates the package versionmpmasksbrewto 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.pipxdelegates to whichever pip lives inside its managed virtualenvs. If that pip predates26.1(or pipx routes resolution throughuvinstead),PIP_UPLOADED_PRIOR_TOis silently ignored.mpmhas 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 removedyarn global, sompmβsyarn-berryhandler only implementssearch. OnboardingnpmMinimalAgeGatewould not change anything reachable throughmpm.apt,snap,fwupdall 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.yayadded user-programmable Lua hooks in v13. AnUpgradeSelecthook can filter the upgrade set by AURLastModifiedage (yay ships an example atdoc/examples/recently_modified.lua), and the maintainer closed the dedicated minimum-release-age requests (Jguer/yay#2824, Jguer/yay#2848) in favor of it.mpmnow drives that machinery for a per-run cooldown by overlaying a generatedinit.luathrough a privateXDG_CONFIG_HOME: the overlay chains the userβs realinit.lua(so nothing in their config is lost) and registers two hooks keyed off the samenow - cooldowncutoff (passed asMPM_COOLDOWN_EPOCH).UpgradeSelectholds back AUR upgrades younger than the floor onyay -Syu, andAURPreInstallaborts a freshyay -Swhose AUR base is younger than the floor, closing the gapUpgradeSelectalone left open (Jguer/yay#2883). This needs yayβ₯ 13; an older yay stays a usable manager but cannot honor the cooldown.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.
The stateless-gate trade-offΒΆ
mpm reads each releaseβs current age at run time and injects the cutoff: it keeps no ledger of past observations. That is safe (a fresh push looks fresh and is held back) but coarse. A package that publishes faster than the cooldown is held back wholesale until its newest version ages in, whereas a stateful tool can still install the most recent version that has already cleared the window. Because mpm trusts the age reported at the moment of the run, it also cannot cross-check that age against earlier observations the way aur-cooldownβs persisted version-to-commit ledger does. Both capabilities need the durable state mpm deliberately forgoes.
Possible future directionsΒΆ
Detect
pipxβs internal pip (or uv) at runtime.mpmβspipmanager has a hard>=26.1.0floor, butpipxmaintains its own virtualenvs whose pip may be older or whose resolution may be routed throughuv(where the right env var isUV_EXCLUDE_NEWERinstead ofPIP_UPLOADED_PRIOR_TO). Probing the resolver per venv would letmpmrefuse to advertise enforcement when the underlying pip is stale.Per-package exemptions (
--cooldown-exclude). The gate is currently all-or-nothing per run, so one legitimately-fresh package (a security fix, a package the user publishes themselves) forces the cooldown off for the whole tree. Both enforcing managers already expose the escape hatch natively: uvβs--exclude-newer-packagetakes aPACKAGE=DATEpair, npmβsmin-release-age-excludetakes names orminimatchglobs. The catch is that this would not fit the uniformcooldown_env_varinjection: uv publishes no environment variable for--exclude-newer-package(only the plain--exclude-newercarries aUV_EXCLUDE_NEWERbinding), so uv needs the exemption appended to the command line while npm can keep taking it throughnpm_config_min-release-age-exclude. Supporting it therefore means a per-manager hook alongside the env var rather than a one-line addition, which is worth knowing before the option is designed.Onboard mechanisms as they ship upstream. Several managers have active work that would slot into the
cooldown_env_varframework 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
outdatedon unsupported managers.mpmcould query each package registry directly (PyPI, RubyGems, crates.io, β¦) to annotateoutdatedwith 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 whichmpmhas today.Consult a curated compromise-window denylist. aur-cooldown pairs its age gate with aur-malware-checkβs
campaigns.json, denying only the versions pushed inside a dated compromise window rather than freezing a package by name. This is orthogonal to release age: a version can be old enough to clear the cooldown yet still sit inside a known-bad window. Wiring such a feed intompmwould lean on the same HTTP client and state directory the advisory mode above already calls for.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ΒΆ
uv
exclude-newerβ the model for the Python ecosystem.npm
min-release-age, shipped innpm11.10.Renovate
minimumReleaseAgeβ delays dependency PRs by a configurable period.aur-cooldown brings the same release-age delay to the Arch AUR, gating on the server-set
LastModifiedage thatmpmβs yay overlay also reads.aur-malware-check publishes
campaigns.json, a community feed of dated AUR compromise windows that aur-cooldown consults to reject versions pushed during a known incident.William Woodruff, We should all be using dependency cooldowns.