Many npm supply chain attacks are detected quickly. Delaying very fresh package versions can block a surprising amount of risk for a small cost.

Package maturity gates: a simple defense against npm supply chain attacks
5 mins
947 words
Loading views

If a package was published 20 minutes ago, maybe your production system should not be the first one to try it.h2

There are many different defense tactics against various javascript library attacks, but one that I think is underappreciated is the idea of a “package maturity gate.”

Modern JavaScript package managers already have features that can enforce exactly that kind of caution.

TLDR: if you only want the practical part, jump to Who supports this today?

They can refuse to install packages that are too fresh.

Not known-malicious packages. Not suspicious packages. Just very new ones.

And that sounds almost too simple, but even forcing packages to age by 1-2 days can cut a lot of risk - 7 recent npm supply chain incidents were caught within hours or a day of the malicious publish.

Very simple rule: do not install dependency versions until they are at least some minimum age.

This does not make a package safe. It does not solve typosquatting, malicious maintainers, or bad code that sat around for months.

But it does help against one very specific and very common problem in the Node.js ecosystem: an attacker compromises a maintainer account or steals a publish token, pushes a malicious version, and people install it before anyone notices.

And that “before anyone notices” window is often shorter than people think.

Why this mattersh2

Most teams are not manually auditing every transitive dependency update.

They run npm install, pnpm install, or bun install, trust the registry, trust the lockfile update, and move on.

That is understandable. Nobody wants to spend their entire week thinking about package provenance.

But supply chain attacks keep showing the same shape:

  • Maintainer gets phished or token gets stolen
  • Malicious version gets published
  • Security researchers or affected maintainers notice suspicious behavior
  • Public disclosure follows shortly after

If your CI or workstation installs during that fresh publish window, you are just unlucky.

So the question is not “how do I make npm perfectly safe?” The question is more practical:

How do I stop being part of the first wave?

Who supports this today?h2

ToolNative maturity gate?Install-script controlsBottom line
BunYes. install.minimumReleaseAge lets you reject versions newer than a configured age, with exclusions for trusted packages. DocsScript controls exist separately, but the maturity gate is the headline feature here.Good built-in protection against brand-new malicious publishes.
pnpmYes. minimumReleaseAge applies age filtering to direct and transitive dependencies. DocsStrong controls: pnpm approve-builds, allowBuilds, and explicit package approval instead of blindly allowing scripts. DocsProbably the strongest native package-manager posture here right now.
DenoYes. minimumDependencyAge is supported in config. Deno 2.6Deno does not run npm lifecycle scripts by default; scripts must be explicitly allowed. DocsVery good defaults, especially because script execution is opt-in.
npmNot a true minimum-age setting, but --before can install package versions that existed on or before a chosen date. Docsignore-scripts is an important mitigation if you do not trust dependency install hooks. DocsBetter than nothing, but less ergonomic than a real minimum-age policy.

If you are on Bun or pnpm, I think this is a “just enable it” kind of feature.

If you are on Deno, you already get a nicer default security posture than the usual Node.js toolchain, especially around install scripts.

If you are on npm, you can still reduce risk with date-based cutoffs, but it is less clean than having an actual minimum-age policy built into normal installs.

Recent incidentsh2

Below is the nicer version of the pattern.

IncidentAttack startedDetected / disclosedApproximate exposure window
Prettier tooling compromise (eslint-config-prettier, eslint-plugin-prettier)July 18, 2025July 19, 2025~1 day
Qix maintainer compromise (debug, chalk-template, color-convert, strip-ansi, others)September 8, 2025September 8, 2025same day
Nx compromiseAugust 26, 2025August 27, 2025~1 day
Shai-Hulud, first waveSeptember 16, 2025September 16, 2025same day
Shai-Hulud, second waveNovember 24, 2025November 24, 2025same day
CanisterWorm / TeamPCP npm wormMarch 21, 2026March 21, 2026same day
Axios maintainer-account compromiseMarch 31, 2026March 31, 2026hours (00:21-03:29 UTC according to Datadog)

The exact numbers are not the interesting part.

The interesting part is that many of these bad publishes were not invisible for weeks. In several cases they were discovered the same day or the next day.

That means a boring delay of 24 hours, 72 hours, or 7 days would have blocked at least some of the blast radius.

Not all of it, but enough to matter.

Package maturity gatesh2

This defense is refreshingly unsophisticated.

Instead of trying to perfectly classify which package is good or bad, you just say: “fresh publishes are higher risk, so we will not consume them immediately.”

That is it.

This works especially well because most projects do not actually need the newest transitive dependency version five minutes after release.

People often act like delaying packages by a few days is unbearable friction. In reality, most teams would not notice unless they are actively chasing a hotfix or testing a brand new release on purpose.

So the trade is pretty good:

  • Small delay for normal installs
  • Big reduction in exposure to brand-new compromised publishes
  • Very cheap to explain and enforce

This is one of the rare defenses that feels asymmetric in your favor.

Attackers need a fresh publish to hit victims quickly. You only need the discipline to say “come back in a day or two.”

This is not enough on its ownh2

Package maturity gates are not a silver bullet.

You still want the usual boring hygiene:

  • Commit lockfiles
  • Minimize install-script execution
  • Pin or carefully review critical dependencies
  • Keep CI visibility on dependency changes
  • Use a trusted internal registry or mirror if your environment justifies it

Still, I like this mitigation because it is simple and cheap.

A lot of security advice sounds good in a slide deck and then quietly dies because nobody will maintain it.

This one is different. It is easy to explain, easy to automate, and it specifically targets the dangerous “just published” window that shows up again and again in npm incidents.

The Node ecosystem probably is not going to stop having supply chain attacks.

So the realistic goal is not perfection. The realistic goal is reducing blast radius.

Package maturity gates are one of the simplest ways to do that.

Comments

Comments are stored on Cloudflare D1 and posted through a Worker API.

Loading comments...
Protection

Be decent. Comments are plain text only.