Skip to content
Kamran Code

npm 12 Blocks Install Scripts. Your Node Doesn't Ship It.

npm 12 blocks dependency install scripts by default, but Node 22, 24 and 26 still bundle npm 10 or 11. My test output, ignore-scripts and the tensorlake case.

Author
Kamran Khan
Published
Reading time
13 min read
On this page
  1. What npm 12 changes
  2. Which Node ships which npm
  3. How to turn install scripts off, or approve one
  4. What the other package managers do
  5. A real case: tensorlake@0.5.144
  6. What this does not protect
  7. What I could not verify
  8. Sources

npm 12, generally available since 8 July 2026, no longer runs a dependency's preinstall, install or postinstall script unless you allow it. Node does not bundle npm 12. Node 22 ships npm 10.9.9, Node 24 and 26 ship npm 11.x, and the Node Release working group decided on 13 August not to land npm 12 on any of the three. So on a stock Node a dependency's scripts still run, unless you upgrade npm or pass --ignore-scripts.

In my test a blocked script gave a warning and exit code 0, so a CI job stays green; --strict-allow-scripts makes npm 12 fail instead. The steps to upgrade, turn scripts off or approve one package are below, along with what other package managers do. Each claim names its source or is marked as my test or my view, and the test used harmless dummy packages I wrote. The real case, tensorlake, comes near the end.

npm 12 blocks install scripts, your Node doesn't (1-minute video)
Transcript

npm twelve blocks install scripts. Your Node doesn't. So what runs on install? The maintainers' advisory says a malicious tensorlake version, published on eight October, carried a preinstall hook. I tested a dummy dependency on Node twenty-four. Its preinstall script ran as me and saw an environment variable I'd planted. npm only warned. Same dependency on npm twelve. Blocked meant skipped, with a warning and exit code zero. The strict allow scripts flag makes the install fail. By their docs, current pnpm skips them and fails the install. Newer Yarn Berry skips them too. But Node's release group decided on thirteen August not to land npm twelve on Node twenty-two, twenty-four or twenty-six. They still ship npm ten or eleven. Install npm twelve, or set the ignore scripts flag. That's my advice. In my reading, neither stops code that runs on import. Check your npm version. If it says ten or eleven, your Node doesn't.

Watch the Short on YouTube: https://youtube.com/shorts/xLSJGpFCu9w.

What npm 12 changes

npm's docs never say which user a script runs as, so I checked on npm 11.16.0, the npm that Node 24.18.0 bundles. My dummy dependency, xl-dummy-dep, points all three hooks at one script. It writes a marker file and prints the user id, the count of environment variables it can see, and the names (never the values) of any that look secret. I set a variable called DEMO_API_KEY first. The hooks ran, npm printed one warning and nothing from the scripts, and only the marker files proved it. With --foreground-scripts the preinstall hook printed this (my test, 15 Oct 2026, Node v24.18.0, npm 11.16.0; prefix and path trimmed, last line split in two):

preinstall RAN as uid=501
env vars visible: 43
secret-looking NAMES: DEMO_API_KEY

It ran as uid 501, my own macOS user and not root, and saw 43 environment variables, including the planted name. Most of the 43 are npm's own npm_* variables, because the test used a cleaned environment. My reasoning, not an npm rule: a script is a child process of npm, so it can read what the installing user can read, such as tokens in ~/.npmrc, files in ~/.ssh, cloud credentials and .env files.

npm 12 switches that default off for dependencies. These are the install-time defaults in GitHub's post, next to what I saw:

Settingnpm 12 defaultMy test
allowScriptsOffSkipped, warning, exit 0
--allow-gitnoneRefused, EALLOWGIT, exit 1
--allow-remotenoneRefused, EALLOWREMOTE, exit 1

The registry shows 12.0.0 published at 21:06 UTC that day, and its release notes say the root package's own preinstall now runs before dependencies are installed.

A block is only a warning, and the install succeeds. These are the first lines of the two warnings from the same dummy package, wrapped for narrow screens (my test, 15 Oct 2026, Node v24.18.0):

npm 11.16.0, scripts ran:
npm warn allow-scripts 1 package
  has install scripts not yet
  covered by allowScripts:
 
npm 12.2.0, scripts blocked:
npm warn install-scripts 1 package
  had install scripts blocked
  because they are not covered by
  allowScripts:

Both say warn. On 11.16 the scripts had already run; on npm 12 they were skipped, no marker files appeared and the exit code was 0. I left out the package line and review hint that follow each warning. Only the strict-allow-scripts entry in the config docs describes the default as blocked with a warning, and the install-scripts docs say install commands skip the scripts and list what was skipped. Neither gives an exit code, so exit 0 is from my test.

npm ci behaved the same way, so a CI job can go green without a package's build having run. My stand-in for a native addon, whose postinstall creates a file that its index.js reads, installed with exit code 0 and then threw ENOENT at require().

--strict-allow-scripts turns the warning into an error: ESTRICTALLOWSCRIPTS, exit code 1, nothing installed. I ran it on npm install and npm ci, on npm 12.0.0 and 12.2.0; npm 11.16.0 accepts the flag too.

Which Node ships which npm

The decision sits in issue 1161 of the nodejs/Release repository. On 13 August 2026 a Release working group member, richardlau, wrote that the group had "decided not to land npm 12 on Node.js 26, 24 or 22". His comment of 23 July gives what appeared to be the group's consensus: the change in security posture is better in the long term but too disruptive for those lines. On 26 August a GitHub npm team member commented that they found the decision unfortunate and asked whether an appeal is possible. They said they were open to discussing what it would take to get npm 12 into Node 26.

Node lineNewest I checkedBundled npmScripts by default
Node 22v22.23.3, 23 Sep10.9.9Run
Node 24v24.22.0, 14 Oct11.19.1Run, warning
Node 26v26.12.0, 14 Oct11.20.0Run, warning

The table comes from the nodejs.org release index and Node's release schedule, which lists Node 26 as Active LTS from 28 October 2026. Node 27's alpha is due the same day, and its main branch carries npm 12.1.0. My Node 24.18.0 (23 June 2026) bundles npm 11.16.0, so the npm 11 half of my test shows what a stock Node does.

You can still run npm 12 on those lines. npm 12.2.0 declares engines of ^22.22.2 || ^24.15.0 || >=26.0.0, which Node 24.18.0 satisfies, and I ran it from a throwaway directory (npm install --prefix <dir> npm@12.2.0). In my view the decision is easy to miss, because it was made in a release-planning thread.

How to turn install scripts off, or approve one

  1. Run node -v and npm -v. If npm says 10 or 11, dependency install scripts run.
  2. Move to npm 12, or turn scripts off yourself. The npm docs give npm install -g npm@latest as the upgrade command, and npm 12.2.0 needs a Node that meets ^22.22.2 || ^24.15.0 || >=26.0.0. I used a throwaway --prefix, so I have not run the -g form. Without upgrading, npm config set ignore-scripts true writes your user .npmrc and skips every script, your own included, and it beat an approved entry in my test. In my view that suits CI jobs better than a daily laptop.
  3. Approve only what needs a build. On npm 12, npm install-scripts ls lists what was blocked and npm install-scripts approve <pkg> approves by name. The older-looking npm approve-scripts <pkg> wrote the same pinned entry on 12.0.0 and 12.2.0 in my test, something like "allowScripts": { "xl-dummy-dep@1.0.0": true } in package.json. Approval is not retroactive, so run npm rebuild or reinstall afterwards.

In a project the policy lives in package.json or as allow-scripts=<pkg> in .npmrc. The --allow-scripts=<pkg> flag is for global installs and npx, and a project install stopped with EALLOWSCRIPTS.

On npm 11.16 and later, npm approve-scripts --allow-scripts-pending lists what is not covered. There, an approval only silences the warning because the scripts run anyway, while a denial (npm deny-scripts <pkg>) blocks that package's scripts without any warning (my test, npm 11.16.0). So on 11.16 a warning does not mean blocked.

In CI on npm 12, run npm ci --strict-allow-scripts so an unreviewed script fails the job. On npm 10 or 11, run npm ci --ignore-scripts, as the tensorlake maintainers did in three workflows (pull request 1016), and npm rebuild <pkg> for the few packages you approve. To get npm 12 into CI or Docker, add the upgrade as a step after actions/setup-node or as a Dockerfile line, and print node -v && npm -v in the job log. I did not test either.

What the other package managers do

The same default exists elsewhere, and the strictness differs. The npm and pnpm rows are from my runs; the Bun and Yarn rows come from their docs.

ToolDependency scripts by default
npm 10 and 11Run; 11.16 and later warn
npm 12Blocked, with a warning and exit 0
pnpm 10Not run, with a warning and exit 0
pnpm 11 and laterNot run, and the install fails
BunNot run, apart from a built-in list
Yarn Berry 4.14 and laterNot run

To allow one package: allowScripts in npm, allowBuilds in pnpm, trustedDependencies in Bun, dependenciesMeta in Yarn Berry. Yarn Classic's install docs state no default.

pnpm stopped running dependency scripts in v10.0.0, and v11.0.0 made strictDepBuilds true by default. In my test, with the dummy package on a local registry, pnpm 10.34.7 skipped the script with a warning and exit code 0, while pnpm 11.0.0 and 12.4.2 failed with ERR_PNPM_IGNORED_BUILDS. A tutorial that sets onlyBuiltDependencies is out of date: pnpm 11 removed it in favour of allowBuilds, as the build settings page says.

Bun's lifecycle docs call their approach default-secure, and file:, git: and github: dependencies need an explicit trustedDependencies entry. Yarn Berry made enableScripts false in 4.14.0, released on 16 April 2026 (pull request 7089), and workspaces' own scripts still run.

A real case: tensorlake@0.5.144

On 8 October 2026 tensorlake@0.5.144 was published to npm through a compromised maintainer account. The maintainers' advisory says it carried valid npm provenance, was live for approximately 1 hour 40 minutes (01:12 to 02:54 UTC) and ran its payload through a preinstall hook. Socket printed the manifest line, "preinstall": "node lib/setup.mjs", and says no SDK import was needed where lifecycle scripts are permitted.

The advisory states, without saying how its authors know, that the payload harvested credentials, republished itself into other npm packages and repositories the stolen tokens could write to, and installed gh-token-monitor, a service that deletes the user's home directory if the stolen GitHub token is revoked. StepSecurity says it decoded the payload without running it, and Sonatype describes a code review. I found no report of a machine being harvested or wiped.

If a machine installed it, the advisory's steps include, in order: disconnect it, remove gh-token-monitor before revoking any GitHub token, rotate credentials from a clean machine, rebuild from a trusted image. The FAQ below covers the lockfile and CI-log check, and my post on a leaked key covers AI provider keys.

On npm 12 defaults that hook is blocked, which is documented behaviour. My test used a dummy package, so I can say the hook would not have run, not that npm 12 would have stopped the attack. In my view a release-age cooldown of a day or more (min-release-age, in npm 11.10 and later, off by default) would have kept this version out, my inference from the advisory's times. Since the payload republishes with the tokens it finds, in my view publish tokens belong in a later, separate job.

What this does not protect

Blocking install scripts does not stop code that runs when you import or execute a package, and the npm docs for ignore-scripts say nothing about import-time code. StepSecurity says the tensorlake payload writes .claude/settings.json and .vscode/tasks.json into repositories it can reach, so it runs again when someone opens the project in Claude Code or VS Code. Sonatype's code review found logic to commit .claude and .vscode files and says the effect depends on how tools load them; I found no report that it happened. That is not an install hook, so a script block does not cover it, and it is one reason I treat third-party skills and MCP servers like installed software (AGENTS.md, skills or MCP).

Native modules still need an approval, because a skipped build fails at runtime, and Socket argues that npm 12 does not close the entry point where a compromised maintainer account publishes through a legitimate pipeline. It adds that an unapproved package's injected install script no longer runs on its own. Tensorlake fits that description, and its provenance showed where it was built, not that it was safe. With --allow-git=all, a git dependency's hooks ran during npm's fetch, in a temporary clone inside the npm cache, although npm then printed that its scripts were blocked. That was one local repository, and allow-git is none by default.

What I could not verify

My tests ran on macOS 26.5.2 with Node v24.18.0, npm 11.16.0, 12.0.0 and 12.2.0, and pnpm 10.34.7, 11.0.0 and 12.4.2, against a local registry and dummy packages. Linux, Windows, workspaces, npx, optional dependencies, implicit node-gyp builds and npm 10 itself are untested.

  • Whether the real tensorlake@0.5.144 carried code that runs on import. I did not download it, and the advisory describes only the install-time hook.
  • How the maintainer account was compromised, and whether npm or GitHub has commented. I found no statement from either.
  • Why npm's publish-time malware scanning, announced on 28 July 2026, did not stop 0.5.144, or whether it was active for that release. No source says.
  • Whether the Node decision will be revisited after the 26 August request.

Sources

All read on 15 October 2026.

Frequently asked questions

Does npm 12 run preinstall scripts?

Not for dependencies, by default. GitHub's 8 July 2026 post says dependency preinstall, install and postinstall scripts, and implicit node-gyp builds, no longer run unless explicitly allowed. The install still succeeds: in my test on npm 12.2.0 npm printed a warning and exited with code 0. Your own project's preinstall is a separate matter, and the 12.0.0 release notes say it now runs before dependencies are installed.

Does Node 24 come with npm 12?

No. Node 24.22.0 (14 October 2026) bundles npm 11.19.1, and the Node Release working group decided on 13 August 2026 not to land npm 12 on Node 22, 24 or 26. You can install npm 12 yourself if your Node satisfies its engines range, which for npm 12.2.0 is ^22.22.2 || ^24.15.0 || >=26.0.0. I ran npm 12.2.0 on Node 24.18.0 from a throwaway directory.

How do I stop npm install scripts?

Upgrade to npm 12, which blocks dependency scripts by default, or turn every script off with --ignore-scripts, on the command line or as ignore-scripts=true in your .npmrc. The second route is all-or-nothing: it also skips your own project's scripts, and in my test --ignore-scripts overrode an approved allowScripts entry. In CI on npm 10 or 11, npm ci --ignore-scripts is the form the tensorlake maintainers added to their workflows.

What is tensorlake 0.5.144?

A malicious release of the tensorlake package on npm, according to its maintainers' advisory GHSA-8g63-53c9-65j2 (8 October 2026, Critical, CVSS 9.6). It had a preinstall hook (Socket printed the manifest line, node lib/setup.mjs), and the advisory says it was live for approximately 1 hour 40 minutes, 01:12 to 02:54 UTC. The maintainers say they have seen no usage of the bad version, by their measure of SDK user-agent headers, which is not a count of installs.

What is the difference between pnpm and npm for install scripts?

pnpm has not run dependency build scripts by default since v10. From v11 its strictDepBuilds setting is true by default, so the install exits with an error when a build is unreviewed. npm 12 also skips the scripts, but by default it only warns and exits with code 0; --strict-allow-scripts makes npm fail. In my test pnpm 10.34.7 warned and exited 0, while pnpm 11.0.0 and 12.4.2 failed with ERR_PNPM_IGNORED_BUILDS.

How do I approve scripts in npm 12?

Run npm install-scripts ls to see what was blocked, then npm install-scripts approve <pkg>. The older-looking npm approve-scripts <pkg> works too: both wrote the same name@version entry under allowScripts in package.json in my test on npm 12.0.0 and 12.2.0. Then run npm rebuild so the approved scripts execute. The --allow-scripts flag is for global installs and npx, and a project install rejects it.

Would npm 12 have stopped tensorlake?

On npm 12 defaults a dependency's preinstall hook like that one is blocked, which is documented behaviour, and the advisory's own remediation applies to installs on a machine or CI system with install scripts enabled. I did not test the real package, so I can say the hook would not have run, not that npm 12 would have stopped the attack. Socket notes that npm 12 does not close the route of a compromised maintainer account publishing through a legitimate pipeline.

How do I check whether I installed tensorlake 0.5.144?

Search your lockfiles (package-lock.json, yarn.lock, pnpm-lock.yaml) for tensorlake@0.5.144 or tensorlake-native-*@0.5.144, and run npm list tensorlake in a project. The advisory also says to review CI logs for any install that resolved to 0.5.144 between 01:12 and 02:54 UTC on 8 October 2026. npm cache ls tensorlake@0.5.144 shows a tensorlake-0.5.144.tgz line if your npm cache holds the tarball; I checked that command on a dummy package.

  • #npm
  • #npm-12
  • #install-scripts
  • #supply-chain-security
  • #nodejs
  • #pnpm
  • #tensorlake
  • #ignore-scripts

Kamran Khan

I build AI-powered applications, full-stack platforms and business software — across full-stack development, business systems, automation and production AI.

About Kamran →

Keep reading

Have a project in mind?

Tell me what you're trying to build. I'll come back with an honest view on scope, architecture and the right approach.