What actually runs at npm install time
npm v12 turned install scripts off by default, so you now approve them one at a time. This is what that approval queue actually looks like across the registry, measured every day.
28run an install script, of 3,238
0.86%of the sample
18score HIGH
255.5Ma week · esbuild
Sticking to popular packages does not help
Show the numbers
| Sample | Download floor | Run an install script | Share | HIGH or MEDIUM |
|---|---|---|---|---|
| Top 100 | 101.9M/week | 1 of 100 | 1.00% | 1 |
| Top 500 | 11.3M/week | 4 of 500 | 0.80% | 3 |
| Top 1,000 | 1.9M/week | 7 of 1,000 | 0.70% | 4 |
| Top 2,000 | 139.0K/week | 18 of 2,000 | 0.90% | 12 |
| Full sample | 4/week | 28 of 3,238 | 0.86% | 18 |
The approval queue
Every package in the sample that runs something, ordered by weekly downloads. None of this is an accusation: these are well-known packages fetching a prebuilt binary or building a native addon. They are simply the ones you are now asked to approve, and "what does it actually do" is a per-package question.
| Package | Downloads/wk | Script | Command | Risk | Signals |
|---|---|---|---|---|---|
| esbuild 0.28.2 | 255.5M | postinstall | node install.js | HIGH | env, exec, fs, net |
| unrs-resolver 1.12.2 | 51.3M | postinstall | node postinstall.js | SAFE | none |
| @sentry/cli 3.8.0 | 23.6M | postinstall | node ./scripts/install.js | HIGH | env, exec, fs, net |
| workerd 1.20260924.1 | 19.0M | postinstall | node install.js | HIGH | env, exec, fs, net |
| vue-demi 0.14.10 | 8.0M | postinstall | node -e "try{require('./scripts/postinstall.js')}catch(e){}" | LOW | fs |
| bufferutil 4.1.0 | 6.8M | install | node-gyp-build | HIGH | bin, env, exec |
| mongodb-memory-server 11.3.0 | 2.1M | postinstall | node ./postinstall.js | SAFE | none |
| node 26.9.0 | 1.4M | preinstall | node installArchSpecificPackage | SAFE | none |
| tree-sitter-json 0.24.8 | 1.1M | install | node-gyp-build | HIGH | bin, env, exec |
| oracledb 7.0.1 | 806.3K | install | node package/install.js | HIGH | obf |
| @pulumi/docker-build 0.0.22 | 806.2K | install | node scripts/install-pulumi-plugin.js resource docker-build 0.0.22 | HIGH | exec |
| tree-sitter-typescript 0.23.2 | 750.2K | install | node-gyp-build | HIGH | bin, env, exec |
| stream-chat 9.53.0 | 496.2K | postinstall | node -e "require('fs').existsSync('scripts/install-husky.mjs') && import('./scripts/install-husky.mjs')" | SAFE | none |
| @azure/msal-node-extensions 5.5.1 | 391.3K | install | exit 0 | SAFE | none |
| redis-memory-server 0.17.1 | 297.2K | postinstall | node ./scripts/postinstall | HIGH | env, exec, fs, net |
| react-native-enriched-markdown 1.0.2 | 214.0K | postinstall | node postinstall.mjs | HIGH | env, exec, exec-local, fs, net |
| classic-level 3.0.0 | 171.7K | install | node-gyp-build | HIGH | bin, env, exec |
| @microsoft/m365agentstoolkit-cli 1.1.17 | 158.2K | postinstall | node deletePS1.js | HIGH | exec |
| nodent-runtime 3.2.1 | 113.4K | install | node build.js | HIGH | fs, obf |
| node-jq 6.3.1 | 102.9K | preinstall | npm run install-binary | HIGH | env, exec, net |
| rete 2.0.6 | 94.7K | postinstall | node postinstall.js | SAFE | none |
| zenstack 2.22.3 | 65.8K | postinstall | node bin/post-install.js | LOW | env |
| tree-sitter-yaml 0.5.0 | 58.2K | install | node-gyp rebuild | HIGH | exec, gyp |
| playwright-ws-trace 1.1.2 | 22.8K | postinstall | node scripts/postinstall.js | LOW | env, fs |
| swc-plugin-import-meta-env 1.16.2 | 21.9K | preinstall | npx only-allow pnpm | HIGH | exec, net |
| bcrypto 5.5.2 | 17.3K | install | node-gyp rebuild | HIGH | exec, gyp |
| better-auth-studio 1.1.5 | 6.3K | postinstall | node scripts/postinstall.js | SAFE | none |
| @qooxdoo/framework 7.9.3 | 4.1K | preinstall | node ./addGitHook | HIGH | env, exec, fs |
Nothing matches that filter.
What they reach for
exec means the script spawns a process, which is exactly what a
native build does.