Every npm install trusts hundreds of strangers at once, and the worms going around npm this year exploit two things in particular: lifecycle scripts that run during the install, and versions published minutes before you pull them.

So the defense is two settings. Refuse dependency install scripts, and refuse any version younger than one day. npm 12 does the first by default. On the npm bundled with Node.js today, add this to the project .npmrc:

ignore-scripts=true min-release-age=1 

Either line alone would have kept tensorlake@0.5.144, the credential-stealing release from October 8, 2026, from running its payload. The problem is that most machines aren't on npm 12. Per the Node.js release list, as of October 14, 2026, Node.js 26.12.0 ships npm 11.20.0, Node.js 24.22.0 LTS ships npm 11.19.1, and Node.js 22.23.3 LTS ships npm 10.9.9. All three still run a dependency's preinstall hook during npm install.

Every output below came from commands run on October 19, 2026. The full guide on DevToolLab goes deeper on each package manager.

The Attack Needed No Import

Tensorlake runs untrusted, LLM-generated code in sandboxes, and its npm SDK sees about 12,000 downloads a week (Socket). Through a compromised maintainer account, an attacker pushed code to the repository and let Tensorlake's own release workflow publish 0.5.144. That's why the bad version had valid npm provenance. It was live from 1:12 AM to 2:54 AM UTC on October 8 (9:12 PM to 10:54 PM ET on October 7), per Tensorlake's advisory.

Tensorlake's GitHub advisory listing tensorlake and six tensorlake-native packages affected at 0.5.144 and patched in 0.5.143, with the 01:12 to 02:54 UTC window

The whole trigger is "preinstall": "node lib/setup.mjs" in the manifest. npm runs it while installing, so no code has to import the SDK. The Bun-launched payload takes npm, GitHub, AWS, Kubernetes and Vault credentials, SSH keys, .env files and coding-agent config (.claude, .cursor and others), then uses any npm token to republish itself. It also plants a gh-token-monitor service that wipes your home directory if the stolen GitHub token is revoked, so remove that service before rotating anything.

Microsoft's ChainDrop analysis (August 4, 2026) counted 400+ packages hit by the same Mini Shai-Hulud worm and the same setup.mjs loader. Install-time code from a fresh version, every time.

Read a Package's Scripts Before You Approve Them

The NPM Package Info tool shows the registry manifest for any name and version. A preinstall, install or postinstall entry under scripts is code npm will run; dist.attestations means provenance. In the test project, an agent that depends on tensorlake, the one to check is protobufjs@7.6.6, with "postinstall": "node scripts/postinstall". It reads metadata only, so it tells you a script exists, not whether it's safe.

Turn Off Dependency Install Scripts

npm 12.0.0, out July 8, 2026, refuses dependency lifecycle scripts unless the root package.json lists them under allowScripts. npm 11.20.0 warns and nothing more. Here is npm 11.20.0 on a fresh project:

$ npm install tensorlake --no-audit --no-fund added 46 packages in 2s npm warn install-scripts 1 package has install scripts not yet covered by allowScripts: npm warn install-scripts protobufjs@7.6.6 (postinstall: node scripts/postinstall) 

Read "not yet covered" as "already ran". On npm 12.2.1 the same install says the script was blocked, and you approve it after reading it:

$ npm install-scripts approve protobufjs Approved protobufjs: added protobufjs@7.6.6 $ npm rebuild rebuilt dependencies successfully 

That writes "allowScripts": { "protobufjs@7.6.6": true } to package.json, pinned to the version you read, so the next protobufjs release needs a fresh approval. Commit it. npm 12 needs Node.js 22.22.2+ on the 22 line, 24.15.0+ on the 24 line, or Node.js 26.

Stuck on npm 10 or 11? ignore-scripts=true in .npmrc blocks every script, including your own project's pre and post hooks, though npm test and npm run build still work (npm config docs). A package that compiles native code in postinstall then needs npm rebuild <name> --ignore-scripts=false once you've read its script. A plain npm rebuild <name> does nothing while the setting is on; we checked.

Wait One Day for New Versions

min-release-age arrived in npm 11.10.0 on February 11, 2026. It makes npm resolve only versions older than the given number of days. One day is enough because worms get caught fast; the pnpm docs say malicious releases are usually found and pulled "within an hour". Tensorlake's timeline bears that out:

Time (UTC), October 8 Event Age of 0.5.144
1:12 AM Published 0 min
1:23 AM Socket flags the preinstall hook 11 min
2:54 AM Removed from npm; GitHub malware advisory goes up 1 h 42 min
1:12 AM, October 9 Earliest a 1-day gate would consider it 24 h

With the gate on, 0.5.144 was never installable. You can see the gate pick the last clean release by stretching the window to 12 days:

$ npm install tensorlake --min-release-age=12 --dry-run --no-audit --no-fund 2>/dev/null | grep tensorlake add tensorlake-native-darwin-arm64 0.5.143 add tensorlake 0.5.143 

Two traps. npm 10.9.9 (Node.js 22 LTS) doesn't know the setting and ignores it silently: with min-release-age=30 it still resolved 0.5.151, published October 14. And if a security fix is younger than your window, npm audit fix keeps the vulnerable version, warns and exits non-zero. Put that package in min-release-age-exclude to let it through.

pnpm, Yarn and Bun Already Do This

pnpm 11.0.0 turned on a one-day gate by default on April 28, 2026, and Yarn 4.18.1 ships with both controls on (we checked with yarn config get). Bun limits scripts but leaves its age gate off.

Tool Dependency install scripts Age setting (unit) Default
npm 10.9.9 Run None None
npm 11.20.0 Run, with a warning min-release-age (days) Off
npm 12.2.1 Blocked until approved in allowScripts min-release-age (days) Off
pnpm 12.14.0 Blocked; our install failed minimumReleaseAge (minutes) 1440
Yarn 4.18.1 Blocked npmMinimalAgeGate (minutes) 1440
Bun Built-in list plus trustedDependencies install.minimumReleaseAge (seconds) Off

Mind the units: 1440 is a day in pnpm and Yarn, 24 minutes in Bun, and nearly four years in npm.

What to Change on Your Setup

  • Node.js 22 LTS, npm 10: add ignore-scripts=true today, then move to npm 12 (needs Node.js 22.22.2+) for both controls.
  • Node.js 24 or 26, npm 11: ignore-scripts=true and min-release-age=1 in the project .npmrc.
  • npm 12: min-release-age=1, approve scripts with npm install-scripts approve, commit allowScripts.
  • pnpm 11+ or Yarn 4.18.1: the defaults cover both. Don't loosen them to get an install through.
  • Bun: minimumReleaseAge = 86400 under [install] in bunfig.toml.

Lockfiles and Provenance Don't Save You Here

The gate acts only when npm picks a version. A lockfile already picked it, and npm ci installs what the lockfile says. With 0.5.151 locked and a seven-day window, npm ci --min-release-age=7 --ignore-scripts installed 0.5.151 without a word. So the protection happens when the lockfile is written, through npm install, npm update, and Dependabot or Renovate pull requests. Review those lockfile diffs like code.

Provenance didn't help either: 0.5.144 came from Tensorlake's real workflow, and Socket found the worm minting Sigstore provenance for what it republished. npm audit signatures (46 verified signatures, 6 verified attestations on the test project) proves where a tarball was built, not that it's safe. npm audit posted the malware advisory at 2:54 AM UTC, as the package came down.

For maintainers: Bloom Security's SlugFest disclosure on October 13, 2026 showed npm trusted publishing matched a repository by its owner/repo name until the first publish, leaving 1,500+ packages open to whoever claimed a renamed or deleted repo's name (The Hacker News). New trusted publisher configs now expire after 2 days without a publish (npm docs); run npm trust list <package> (npm 11.15.0+) to check yours.

Your Coding Agent Runs npm install Too

The tensorlake payload looks for .claude, .cursor and other agent config and MCP files, and Microsoft saw ChainDrop write Claude and VS Code config files into repositories it could push to. Agents also install packages on their own, and Bloom Security notes they tend to grab the latest version.

A project .npmrc covers installs inside the repo no matter who types the command. npx runs and global installs have no project, so set the gate at the user level as well:

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

npx respects it: with a 30-day window in the user config, npx -y pnpm@12 --version printed 12.5.1 rather than the current 12.14.0.

Errors You'll Hit

  • npm error notarget No matching version found ... with a date before ...: an exact version younger than the window was requested. Wait, go older, or use min-release-age-exclude.
  • npm warn Unknown project config "minimum-release-age": pnpm's spelling, which npm ignores. Use min-release-age.
  • npm error --allow-scripts is not allowed in project-scoped installs.: in a project, approve with npm install-scripts approve <pkg>; the flag is for npx and global installs.
  • ERR_PNPM_IGNORED_BUILDS: strictDepBuilds stopped the install. Approve with pnpm approve-builds, or decide per package in allowBuilds inside pnpm-workspace.yaml.

The original has the exact text of each message and a few more edge cases in its common errors section.

What These Settings Cost

A one-day window delays fixes as much as worms, hence the exclude lists. Blocking scripts breaks packages that build native binaries in postinstall, so expect to approve a few. And neither setting stops a package that attacks at import time or a bad version that stays up for weeks; that takes lockfile review, the scanners in this supply chain security tools roundup, and narrow tokens. The Secret Scanner finds npm, GitHub and AWS keys sitting in code or config before a worm does.

The Short Version

Every attack here ran install-time code from an hours-old version. Add the two .npmrc lines today, move to npm 12 for reviewed allowScripts approvals, upgrade npm first on Node.js 22 LTS, and keep reading lockfile diffs, since the gate never rechecks a locked version.

References