Protecting Against Compromised Packages with Minimum Release Age

Protecting Against Compromised Packages with Minimum Release Age
Nicolas Charpentier
Nicolas Charpentier
April 29, 2026
6 min read

We're living in a weird era.

On one hand, we constantly have to update our dependencies to fix CVEs. I even wrote a whole post on doing it properly. On the other hand, every week or so, we wake up to the news that yet another popular package was compromised, hijacked, or had a malicious version published to the registry. The advice is essentially: "update your dependencies, but not too fast, and definitely not the wrong ones". Make it make sense.

The good news is that our package managers have started to ship features specifically aimed at this problem. Let's talk about one of them: minimum release age.

The Idea

Most malicious releases are caught quickly. Maintainers spot the unauthorized publish, the registry takes the package down, security researchers raise the alarm, and the version is unpublished or deprecated, usually within hours. So the simplest mitigation is also one of the most effective: don't install brand new versions immediately. Wait a bit. Let the community find the bad ones first.

And there's a great community out there doing exactly that. Tools like Socket, Snyk, and Aikido actively scan the npm registry for malicious behavior (suspicious install scripts, obfuscated code, exfiltration patterns, typosquats, and so on), often catching compromised packages within minutes of publication. Socket in particular has been incredibly vocal about this, publishing detailed write-ups of nearly every major supply chain incident, which is what makes the "wait a bit" strategy actually work. The faster these tools (and the security researchers behind them) flag a bad release, the safer your delayed-install policy becomes.

That's exactly what pnpm's minimumReleaseAge (and its equivalents in other package managers) lets you take advantage of. You define a minimum age (say, 24 hours, or 3 days), and your package manager will refuse to install any version that was published more recently than that, including transitive dependencies.

It's not a silver bullet. A patient attacker could publish a malicious version, sit on it for a week, and still get picked up. But in practice, the overwhelming majority of supply chain incidents we've seen are noticed within the first few hours, precisely because of the work these scanners and the broader security community do.

pnpm

If you're using pnpm 10.16+, you can configure it directly in your pnpm-workspace.yaml:

pnpm-workspace.yaml
minimumReleaseAge: 1440

The value is in minutes, so 1440 means one day.

This applies to all dependencies, including transitive ones, which is exactly what you want. Most supply chain attacks happen via transitive dependencies you didn't even know you had.

Note

Setting this on an existing project won't break anything immediately. Your lockfile already pins specific versions, so installs will keep using those. The setting kicks in the next time you add or update a dependency.

Excluding specific packages

Sometimes you genuinely need the latest version of something right away. Maybe you're the one publishing it, or you're chasing a fix for an issue that just landed. For that, there's minimumReleaseAgeExclude:

pnpm-workspace.yaml
minimumReleaseAge: 1440
minimumReleaseAgeExclude:
  - webpack
  - react
  - '@myorg/*'

You can exclude specific package names, glob patterns (like an entire scope), or even specific versions:

pnpm-workspace.yaml
minimumReleaseAge: 1440
minimumReleaseAgeExclude:
  - nx@21.6.5
  - webpack@4.47.0 || 5.102.1
Tip

Excluding your own org (@myorg/*) is a common pattern. You generally trust your own publishes, and you don't want internal releases to be delayed before they can be consumed by other projects.

Yarn Berry

Yarn Berry 4.10+ has the equivalent setting under a slightly different name: npmMinimalAgeGate. It accepts a duration string rather than a number of minutes, which I personally find a lot more readable:

.yarnrc.yml
npmMinimalAgeGate: "3d"

To preapprove specific packages or patterns:

.yarnrc.yml
npmMinimalAgeGate: "3d"
npmPreapprovedPackages:
  - "@myorg/*"
Tip

3d is a sensible value here, partly because the npm registry has specific rules for packages less than 72 hours old (they can still be freely unpublished, which would also break your build).

npm

npm 11 introduced the min-release-age config, with the value in days:

.npmrc
min-release-age=1
Warning

min-release-age is mutually exclusive with the before config. They solve overlapping problems (before pins to an exact date, min-release-age is relative), but you can't combine them.

npm doesn't currently have a built-in exclusion mechanism, so it's a bit more all-or-nothing than pnpm or Yarn.

Bun

Bun 1.3+ has the same setting, install.minimumReleaseAge, configured in bunfig.toml. The value is in seconds:

bunfig.toml
[install]
# Only install package versions published at least 3 days ago
minimumReleaseAge = 259200

Exclusions work via install.minimumReleaseAgeExcludes:

bunfig.toml
[install]
minimumReleaseAge = 259200
minimumReleaseAgeExcludes = ["@types/bun", "typescript"]
Note

Each package manager uses a different unit: pnpm uses minutes, Yarn uses duration strings (3d), npm uses days, and Bun uses seconds. Double-check the value when copying configurations between projects.

Picking a Value

How long should you wait? There's no perfect answer, but here's how I think about it:

  • 24 hours (1 day) is a solid baseline. It catches the vast majority of malicious releases that get pulled within a few hours, with minimal disruption to your workflow.
  • 72 hours (3 days) is more conservative. It also dodges the npm unpublish window, which is nice for build reproducibility.
  • 7 days is paranoid mode. Probably overkill for most teams, but reasonable if you're shipping software to customers who have very low tolerance for incidents.

The right value really depends on how quickly you need to consume new releases versus how much you trust the ecosystem on any given week. And lately... well, the answer to that second part keeps shifting.

Tip

Don't forget to combine this with other defenses: lockfiles, proper CVE resolution, ignoreScripts for transitive dependencies (or pnpm's onlyBuiltDependencies / allowBuilds), provenance attestations, and 2FA on your own publishes. None of these alone is enough, but together they raise the cost of an attack significantly.


minimumReleaseAge (and friends) is one of the rare settings that's almost free. You add a few lines to your config, and you get meaningful protection against a category of attacks that has become depressingly common. There's no real downside other than not getting the latest version of something the second it's published, which, given the alternative, is a tradeoff most of us should be very happy to make.

Add it to your config today. It's one of those things you'll forget you turned on until the day it saves you.

hourglass