A postinstall script is arbitrary code that npm 2,036 runs on your machine, as you, during installation — before you have read a line of the package. Native addons need it to compile; so does every credential stealer ever published to npm. Here is one whose scripts field is { "postinstall": "node steal.js" }:
> npm install --foreground-scripts > evil-parser@1.0.0 postinstall > node steal.js postinstall running as fragm in C:\tmp\hook-demo\vendor\evil-parser added 1 package in 599ms
That process has your user account, home directory, SSH keys, .npmrc token and network. --ignore-scripts suppresses it and the install still succeeds. Set it project-wide, then opt packages back in with npm rebuild sharp.
npm config set ignore-scripts true --location=project
npm config set min-release-age 3
npm config set min-release-age-exclude "@acme/*"ignore-scripts defaults to false in npm 11.11.0, and it blocks your own package's scripts too, so a prepare step that builds your TypeScript must move into an explicit npm run build. pnpm 69,400 took the opposite default from version 11: build scripts need per-package approval through allowBuilds, strictDepBuilds is true so an install fails on unreviewed ones, and dangerouslyAllowAllBuilds is false.
The second install-time attack needs no script at all: publish expresss or lodahs and wait for a typo, or phish a real maintainer's account and push a malicious patch release. Both are defeated by waiting, which is what the other two commands buy. min-release-age is a number of days, default null; with it set, npm builds the tree only from versions available more than that many days ago — usually long enough for a bad release to be reported and pulled — while min-release-age-exclude keeps your own packages moving. When a fix lands inside the window npm keeps the vulnerable version, warns that the fix was blocked and exits non-zero, so a stuck npm audit fix in CI is a signal rather than a bug.