Install scripts are where npm supply-chain attacks run: code that executes on your machine, or your CI runner, before you have imported anything. Rather than guess how much of the ecosystem does this, we counted, and for the packages that do, we ran their install steps and watched.
How we did it
The list is the 5,000 most-downloaded packages from npm-high-impact 1.13.0. We read the latest version of each from the npm registry on 8 October 2026 (4,995 manifests read, 5 failed). Every package that declares preinstall, install or postinstall, or ships a binding.gyp, went through the Kenwea notary's production sandbox: no network, all Linux capabilities dropped, a read-only filesystem, not root, installed inside node_modules of a project that depends on it with the variables npm gives install scripts, 15 seconds per step, and each step under strace for the network connections, DNS lookups and programs it attempted. Dependencies were not installed.
Only 31 of 5,000 run anything at install
That is 0.6 percent. For almost every popular package, installing it runs none of its own code. The install-time surface of the ecosystem is concentrated in a short list, small enough for a team to read by hand.
What the 31 did
- 13 ran to completion and tried to reach nothing: core-js, core-js-pure, es5-ext, vue-demi, nx, puppeteer, yarn, @google/genai (its preinstall only prints a line), @firebase/util, @parcel/watcher, ssh2, @scarf/scarf and @anthropic-ai/claude-code.
- 13 failed, nearly all because a native build wants a compiler, Python, or a prebuilt binary that ships in a dependency we did not install: pnpm, @pnpm/exe, unrs-resolver, @sentry/cli, msgpackr-extract, node-pty, cypress, cpu-features, canvas, bufferutil, utf-8-validate, lmdb and bcrypt.
- 3 were stopped at our 15-second limit while starting a package manager to fetch something: esbuild and @swc/core fall back to running npm install for their platform binary, and union's preinstall runs npx npm-force-resolutions. None of them had sent a DNS query by the time they were stopped.
- fsevents is listed with node-gyp rebuild in the registry, but its tarball ships a prebuilt binary and no binding.gyp, so nothing runs. It is macOS-only in any case.
- aws-sdk unpacks to more than the sandbox's 96 MiB working space and was not run. The public notary does not fetch packages over 10 MiB, so aws-sdk and node-pty cannot be checked there at all.
What this says, and what it does not
- No package tried to reach the network during its own install step here. That is not the same as safe. Dependencies were not installed, so steps that download after a fallback were cut short; a script can notice it is in a sandbox and stay quiet; and @scarf/scarf, whose job is telemetry, reports only when it is another package's dependency, which a check of the package alone never is.
- The useful part is the concentration. 31 install steps can be reviewed by people. 5,000 packages cannot.
- If your project does not need install scripts, npm install --ignore-scripts turns them off, and pnpm 10 already blocks dependencies' lifecycle scripts unless you allow them by name.
Check one yourself
Every result above can be reproduced, and each comes back as a record signed with Kenwea's published key and verified on your machine before it is shown. In CI, a gate fails the build when a dependency runs anything at install or tries the network:
npx -y @kenwea/mcp check esbuild
npx -y @kenwea/mcp check esbuild --fail-on install-scripts,networkRaw results for all 31 packages, with the declared scripts, the outcome and what each step attempted: https://www.kenwea.com/data/census-2026-10-08.json. The run took 171 seconds on 8 October 2026. Package versions change daily, so a rerun will differ.
Try the notary with no account or key, or add it to your agent as an MCP server.