deniz.in

Markets

Weather

Loading weather

· via dev.to (home feed)

Delaying fresh installs: 7-day package gates for npm, pnpm, Yarn, Bun and uv

Malicious npm releases often live for hours, not weeks. A dev.to guide explains how to enforce release-age cooldowns in npm, pnpm, Yarn, Bun and uv so coding agents stop pulling brand-new package versions.

Delaying fresh installs: 7-day package gates for npm, pnpm, Yarn, Bun and uv

Malicious package releases on npm tend to have short lives. According to a dev.to post by Gabriel Anhaia, the malicious chalk release stayed up for under 12 hours, one targeting Nx for about four hours, and one for rspack for roughly an hour. Reviewing ten well-known supply-chain attacks, William Woodruff found that eight had an exploitation window of less than a week.

That window is exactly where autonomous coding agents operate. An agent decides it needs a library, runs an install seconds later, and receives the newest published version — one that no scanner and possibly no one else has reviewed, potentially pushed from a maintainer account phished an hour earlier. Anhaia's proposed defence is a release-age gate, or cooldown: refuse any version younger than N days. npm, pnpm, Yarn, Bun and uv can all enforce one now, each spelling the setting differently.

One setting, five dialects

The seven-day gate in each manager:

  • npm (11.10.0 and later): min-release-age=7 in .npmrc, counted in days.
  • pnpm (10.16 and later): minimumReleaseAge: 10080 in pnpm-workspace.yaml, counted in minutes.
  • Yarn (4.10.0 and later): npmMinimalAgeGate: "7d" in .yarnrc.yml, written as a duration.
  • Bun (1.3 and later): minimumReleaseAge = 604800 under [install] in bunfig.toml, counted in seconds.
  • uv (0.9.17 and later): exclude-newer = "7 days" in pyproject.toml, also a duration.

Getting the unit wrong makes the gate useless or absurd, Anhaia warns: minimumReleaseAge: 7 means seven minutes in pnpm and seven seconds in Bun, while min-release-age=10080 in npm means about 27 years.

npm accepts the key and ignores it

min-release-age only arrived in npm 11.10.0, released 11 February 2026. Older npm versions read the .npmrc, fail to recognise the key, and install with no gate at all; from 11.2.0 there is at least an unknown-config warning. Anhaia recommends a pre-install script that fails the build on any npm older than 11.10.

Per the npm documentation he cites, the resolver builds a dependency tree from versions published more than the configured number of days ago, and errors when nothing qualifies. If npm audit fix is blocked because a patch is too new, npm keeps the vulnerable version, warns, and exits non-zero — the signal to add a deliberate exclusion. Only exempt scopes you actually own on the public registry; otherwise anyone can publish @myorg/anything and walk past the gate.

pnpm, Yarn and Bun

pnpm counts in minutes and, since pnpm 11, reads the setting from pnpm-workspace.yaml. It defaults to 1440 minutes — one day — but strict enforcement only applies when you set the value yourself. With the default, pnpm may fall back to a too-new version when nothing older fits; with an explicit value, resolution fails instead. Its exclude list accepts names, globs and exact pins such as [email protected] || 5.102.1.

Yarn calls its exclusions pre-approved packages, configured via npmPreapprovedPackages, and stores the gate in minutes internally — in Classmethod's testing, "21d" read back as 30240. For a version range, Yarn installs the newest candidate that passes; the gate also covers transitive dependencies, and an exact pin that is too new fails with "No candidates found".

Bun's minimumReleaseAgeExcludes takes package names, or name@version to exempt one exact release. The documentation shows no scope wildcard, so internal packages must be listed individually.

uv freezes the cutoff into the lockfile

uv's exclude-newer accepts friendly durations like "7 days" and ISO 8601 forms like "P7D" — but not months or years — while exclude-newer-package can exempt a single internal package. Three behaviours differ from the JavaScript managers. The duration is converted to a timestamp at resolution time and written into uv.lock, where it stays until the next explicit resolution such as --upgrade, so the lockfile does not drift every day. The cutoff is checked against each file's upload time rather than the version's release date, so a wheel uploaded yesterday against an old version still counts as too new. And only registry packages are gated — Git dependencies are not checked.

The gate belongs in a committed file

User-level configuration such as ~/.npmrc reaches every worktree on one machine, but not a teammate's laptop, CI or a container. Anhaia therefore puts the gate in a committed file so any agent running an install in any checkout of the repository reads it, and keeps registry tokens in the user-level file since the project file goes into git. Writing from his own perspective as the developer of the agent-oriented Hermes IDE, he adds that per-task worktrees inherit the committed setting automatically — a point that also promotes his product, so weigh it accordingly.

Why it matters

Supply-chain attacks increasingly depend on speed: publish, collect installs, vanish before takedown. AI coding agents are ideal victims because they choose and install dependencies in the same breath, with no human pause in between. A seven-day cooldown removes most of that exposure almost for free; the cost is waiting for legitimate patches, which the exclusion mechanisms exist to manage. The broader lesson is operational: a config line is not a policy until something enforces it. Older npm silently ignores the key, pnpm only fails closed when the value is explicit, and every tool names and measures the gate differently. Anyone adopting this should add a version check to CI and a test proving that a fresh release is actually blocked, not just configured against.

  • #npm
  • #supply-chain-security
  • #package-managers
  • #coding-agents
  • #javascript

Related posts