Dev.to · 4 min read

npm 12 Blocks Install Scripts by Default. Node 26 LTS, Out Oct 28, Still Ships npm 11.

npm 12 Blocks Install Scripts by Default. Node 26 LTS, Out Oct 28, Still Ships npm 11.

On October 8, tensorlake@0.5.144 was on npm for about 1 hour 40 minutes. It was not a real release. Someone with access to the maintainer's GitHub account committed a payload and ran the project's own release workflow, so the package even carried valid npm provenance. A preinstall hook ran the payload the moment it was installed on a developer machine. The payload went after GitHub and npm tokens, AWS and GCP credentials, SSH keys, .env files and browser passwords, then used those tokens to republish itself into other packages. It also installed a small service called gh-token-monitor that deletes your home directory if the stolen GitHub token gets revoked (SafeDep found it only arms for tokens on accounts with no organizations). That's all in the maintainers' own GitHub advisory (GHSA-8g63-53c9-65j2), and it's part of the Mini Shai-Hulud campaign that has been hitting npm since April. npm already has a fix for the "runs on install" part. npm 12, out since July 8, doesn't run dependency preinstall, install or postinstall scripts unless your project allows them. Here's the catch. Node 26 became LTS on October 28, and the npm it ships is 11.20.0. That's not npm 12. I checked the bundled npm version for every current LTS line in the Node release index today: Node release Date npm it ships 26.13.0 (LTS "Lithium") Oct 28, 2026 11.20.0 24.22.2 (LTS "Krypton") Oct 27, 2026 11.19.1 22.23.4 (LTS "Jod") Oct 20, 2026 10.9.9 The only Node line with npm 12 bundled right now is the 27 alpha. So if you install Node the normal way and never run npm install -g npm, you're on a version that still runs every install script in your tree. What each package manager actually did I wanted to see this rather than trust release notes, so on a scratch box I made a tiny local package whose preinstall writes a marker file to the temp dir. I installed it next to esbuild (a normal, harmless package that has a postinstall) with each tool, then checked whether the marker file existed. Tool Script ran? What it printed npm 10.9.9 (what Node 22 ships) yes nothing at all npm 11.21.1 yes a warning listing both packages npm 12.2.1 no "2 packages had install scripts blocked" pnpm 10.34.6 no "Ignored build scripts: esbuild@0.25.11, harmless-preinstall@file:..." bun 1.4.2 no "Blocked 1 postinstall" Two things stood out. On npm 10 there's no hint anything ran. On npm 11 the warning reads like a heads-up about the future, but the marker file was there, so the script had already run. The script also had a console.log, and that line never showed up in the install output on npm 10 or 11. You don't see install scripts run. pnpm and bun already block by default. If you're on either, most of this post is about your CI and your teammates, not you. Check a project in 10 seconds This prints your npm version, whether install scripts are on, which packages in your lockfile actually have install scripts (npm records hasInstallScript in package-lock.json), and whether any of the tensorlake persistence files exist on the machine. #!/usr/bin/env bash # install-scripts-check.sh: who gets to run code when you type npm install? # usage: bash install-scripts-check.sh [project-dir] (needs jq) cd "${1:-.}" || exit 1 v=$(npm -v); major=${v%%.*}; ign=$(npm config get ignore-scripts) allow=$(jq -c '.allowScripts // empty' package.json 2>/dev/null) echo "node $(node -v), npm $v, ignore-scripts=$ign" if [ "$major" -ge 12 ]; then echo "OK npm 12+: dependency install scripts blocked unless allowed" elif [ "$ign" = "true" ]; then echo "OK ignore-scripts=true: install scripts off" else echo "WARN npm $major runs every dependency install script (it only warns)"; fi [ "$ign" = "true" ] && [ -n "$allow" ] && echo "WARN ignore-scripts=true silently overrides allowScripts" echo "allowScripts: ${allow:-none}" if [ -f package-lock.json ]; then n=$(jq '[.packages | keys[] | select(. != "")] | length' package-lock.json) echo "lockfile: $n packages; these have install scripts:" jq -r '.packages | to_entries[] | select(.value.hasInstallScript == true) | " \(.key | sub("^.*node_modules/"; ""))@\(.value.version)"' package-lock.json fi echo "tensorlake worm persistence (all should be missing):" for f in ~/.config/gh-token-monitor ~/.local/bin/gh-token-monitor.sh \ ~/Library/LaunchAgents/com.user.gh-token-monitor.plist; do if [ -e "$f" ]; then echo " FOUND $f

This is a summary aggregated from Dev.to. Read the complete article on the original site:

Read full article at Dev.to

More AI & Machine Learning News