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
| Tool | Native maturity gate? | Install-script controls | Bottom line |
|---|---|---|---|
| Bun | Yes. install.minimumReleaseAge lets you reject versions newer than a configured age, with exclusions for trusted packages. Docs | Script controls exist separately, but the maturity gate is the headline feature here. | Good built-in protection against brand-new malicious publishes. |
| pnpm | Yes. minimumReleaseAge applies age filtering to direct and transitive dependencies. Docs | Strong controls: pnpm approve-builds, allowBuilds, and explicit package approval instead of blindly allowing scripts. Docs | Probably the strongest native package-manager posture here right now. |
| Deno | Yes. minimumDependencyAge is supported in config. Deno 2.6 | Deno does not run npm lifecycle scripts by default; scripts must be explicitly allowed. Docs | Very good defaults, especially because script execution is opt-in. |
| npm | Not a true minimum-age setting, but --before can install package versions that existed on or before a chosen date. Docs | ignore-scripts is an important mitigation if you do not trust dependency install hooks. Docs | Better 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.
| Incident | Attack started | Detected / disclosed | Approximate exposure window |
|---|---|---|---|
Prettier tooling compromise (eslint-config-prettier, eslint-plugin-prettier) | July 18, 2025 | July 19, 2025 | ~1 day |
Qix maintainer compromise (debug, chalk-template, color-convert, strip-ansi, others) | September 8, 2025 | September 8, 2025 | same day |
| Nx compromise | August 26, 2025 | August 27, 2025 | ~1 day |
| Shai-Hulud, first wave | September 16, 2025 | September 16, 2025 | same day |
| Shai-Hulud, second wave | November 24, 2025 | November 24, 2025 | same day |
| CanisterWorm / TeamPCP npm worm | March 21, 2026 | March 21, 2026 | same day |
| Axios maintainer-account compromise | March 31, 2026 | March 31, 2026 | hours (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.