Locking down dependency installs across npm, pnpm, yarn, and bun

May 29, 2026

A minimum release age (or “cooldown”) instructs a package manager to ignore any dependency version published less than a configured amount of time ago. The reference below covers the cooldown, install-time lifecycle script gating, and pnpm’s trust policy across the four major Node.js package managers; the setting names and units differ in each.

Table of Contents

Overview
#

A cooldown delays a freshly published version from being installable. Compromised releases are usually flagged and removed from the registry within hours of publication, so even a 24-hour delay filters most of those incidents at the install layer without monitoring or scanning.

Each tool exposes the setting under its own name and unit of time:

  • npm: min-release-age, in days. npm CLI 11.10.0 (February 2026).[1]npm CLI documentation, min-release-age.
  • pnpm: minimumReleaseAge, in minutes. pnpm 10.16 (September 2025); default 1440 since pnpm 11.0.[2]pnpm documentation, minimumReleaseAge. Notes the default of 1440 minutes since pnpm 11.
  • Yarn: npmMinimalAgeGate, in minutes. Yarn Berry 4.10.0.[3]Yarn documentation, npmMinimalAgeGate.
  • Bun: minimumReleaseAge, in seconds. Bun 1.3.0.[4]Bun documentation, install.minimumReleaseAge.

The setting can be written per-project or per-user. A per-project value protects the codebase against newly published policy violations; a user-wide value extends the cooldown to repositories that have not opted in. Both are worth setting; neither replaces the other, and the conditions under which either is silently bypassed are listed in the caveats below.

A cooldown is also only one of the install-time controls the package managers now ship. Lifecycle scripts covers disabling or allow-listing install-time script execution, and pnpm’s trust policy covers rejecting releases whose provenance regressed.

Per-project
#

Per-project configuration lives in the package manager’s project-local config file and is committed to the repository. The selected tab is remembered across the rest of the post.

The setting belongs in the project’s .npmrc:

ini.npmrc
min-release-age=1

The value is in days. The setting cannot be combined with --before in the same invocation; npm raises an error if both are provided. An npm CLI version that does not recognize the key emits the following warning, which is cosmetic and does not prevent the setting from applying on a supporting CLI:

npm warn Unknown project config "min-release-age". This will stop working in the next major version of npm.

User-wide
#

User-wide configuration applies on a single machine, across every repository that does not override it.

bash
npm config set min-release-age 1 --location=user

This writes to ~/.npmrc. Substituting global for user writes to the global npm configuration instead.

Lifecycle scripts
#

A cooldown controls when a version becomes installable; it says nothing about what the package does once it lands. Install-time lifecycle scripts (preinstall, install, postinstall) run arbitrary shell commands during install, which makes them the primary payload mechanism in npm supply-chain attacks — the malicious code in a compromised release typically runs from a postinstall hook, not from anything the application imports. All four tools can disable these scripts outright or gate them behind a per-package allowlist. The granular options are recent additions, so the pinned-version caveat applies here just as much as it does to the cooldown.

The blunt instrument is ignore-scripts, which works on every npm version:

ini.npmrc
ignore-scripts=true

This disables lifecycle scripts for all dependencies, but it also affects the project’s own hooks: npm run, npm start, and npm test still execute the named script, but skip its pre- and post-scripts.

npm 11.16.0 adds a granular alternative: an allowScripts field in package.json, managed with npm approve-scripts and npm deny-scripts.[11]npm CLI documentation, npm approve-scripts. Manages the allowScripts field, available since npm 11.16.0. On npm 11 the field is advisory — unreviewed install scripts still run, and the install prints a warning listing them. Enforcement is opt-in:

ini.npmrc
strict-allow-scripts=true

npm v12 (estimated July 2026) makes enforcement the default: install scripts from dependencies will not run unless explicitly allowed, and git and remote-URL dependencies will not resolve unless --allow-git or --allow-remote is granted.[12]GitHub community discussion, "Preparing for npm v12: install scripts and non-registry sources become opt-in".

pnpm’s trust policy
#

pnpm 10.21 adds a check orthogonal to both the cooldown and script gating. With trustPolicy set to no-downgrade, the install fails if a package’s trust level has decreased relative to its earlier releases — for example, a package whose previous versions carried provenance attestations suddenly shipping a version without one.[17]pnpm documentation, trustPolicy. Added in pnpm 10.21, alongside trustPolicyExclude (10.22) and trustPolicyIgnoreAfter (10.27). That signature is characteristic of a stolen-token compromise: the attacker publishes from their own machine, and the provenance that the maintainer’s CI pipeline would have attached is missing.

yamlpnpm-workspace.yaml
trustPolicy: no-downgrade

Two companion settings handle false positives. trustPolicyExclude (pnpm 10.22) takes package selectors to skip, for packages that legitimately changed how they publish. trustPolicyIgnoreAfter (pnpm 10.27) skips the check for versions older than a given number of minutes — the cooldown’s reasoning applied in reverse: a release that has been public for weeks without being pulled is unlikely to be the compromise this check exists to catch. Like the cooldown, the setting can be written per-project in pnpm-workspace.yaml or user-wide with pnpm config set trustPolicy no-downgrade --location=user.

Caveats
#

A handful of conditions can quietly bypass the configured cooldown or change what is actually being honored.

The check runs at install time, not when an updater opens a PR. Renovate and Dependabot evaluate updates independently of the package manager’s cooldown, so their own cooldown options must be set separately. Both ship reasonable defaults: Renovate’s config:best-practices preset waits three days before raising an npm update,[5]Renovate documentation, config:best-practices preset. Includes security:minimumReleaseAgeNpm, which waits three days before raising an npm update. and Dependabot exposes cooldown.default-days with semver-specific overrides.[6]GitHub Docs, Dependabot cooldown configuration, covering default-days and the semver-specific overrides.

A repository’s pinned package manager is what actually runs the install. A pin via the packageManager field, Corepack, Volta, a Docker base image, or setup-node with version-file overrides whatever is installed globally. If the pinned version predates cooldown support, every layer of configuration above it is inert. The most reliable check is to run pnpm --version (or the equivalent) from inside the repository, or to inspect the setup-node step in the CI log.

Project-level configuration takes precedence over user-level configuration in all four tools. A user-wide cooldown of one day does not apply inside a repository whose project config sets a smaller value or none at all. A user-wide value is not a backstop for repositories that have not opted in.

The user-wide file lives at a different path per operating system for pnpm and Bun. npm and Yarn read ~/.npmrc and ~/.yarnrc.yml from the home directory everywhere, but pnpm’s global config.yaml moves between ~/.config/pnpm/ (Linux), ~/Library/Preferences/pnpm/ (macOS), and ~/AppData/Local/pnpm/config/ (Windows),[19]pnpm documentation, pnpm config. Lists the per-OS locations of the global config.yaml and its sibling rc file. and Bun switches from ~/.bunfig.toml to $XDG_CONFIG_HOME/.bunfig.toml when the variable is set. A config file hand-written to the path from another operating system’s documentation is silently ignored. Writing through the tool’s own CLI and then reading the value back from outside any project confirms the setting landed somewhere it will actually be read.

None of the four tools support per-registry scoping. Internal packages from a private registry are subject to the cooldown unless explicitly listed in the per-tool exclusion option. npm does not yet expose one; an issue is open against the CLI tracking it.[7]npm CLI issue #8994. Tracks an exclusion list for min-release-age.

Defaults are shifting upward. pnpm 11 ships with the gate enabled at 1440 minutes by default,[2]pnpm documentation, minimumReleaseAge. Notes the default of 1440 minutes since pnpm 11. and npm v12 is set to make install scripts and non-registry dependency sources opt-in.[12]GitHub community discussion, "Preparing for npm v12: install scripts and non-registry sources become opt-in". Setting explicit values insulates the configured behavior against changes elsewhere in the ecosystem.

Acknowledgements
#

This digest closely follows two gists that did the original work of pulling the per-tool settings together: