Run npm install on a mid-size project. Watch the terminal. "added 847 packages in 12s."
Those 847 packages can each run shell commands during installation. Preinstall, install, postinstall hooks that fire automatically with your user permissions. The same permissions that read ~/.ssh/, ~/.aws/credentials, and your browser profiles. No prompt. No sandbox. You hit enter and 847 strangers get to execute whatever they want on your machine.
Most developers have never spent five seconds thinking about this.
This Keeps Happening
Install hooks have been the primary entry point for npm supply chain attacks.
eslint-scope (2018): A compromised npm account pushed a version with "postinstall": "node ./build.js". Harmless-looking. That file read ~/.npmrc and POSTed your npm tokens to a pastebin. About 4,500 downloads in under 72 hours before anyone noticed.
crossenv (2017): A typosquat of cross-env. One missing hyphen, same README. The postinstall script sent every environment variable on your machine to an external server. Over 700 developers installed it.
Axios compromise (2026): "postinstall": "node install.js". That file detected your OS, downloaded a platform-specific RAT, ran it, and deleted itself. The package had 100 million weekly downloads.
The pattern repeats. A lifecycle script runs something that sounds like a build step. The referenced file contains the payload. Everything fires during npm install with zero interaction from you.
You Can't Just Turn Them Off
The usual advice is npm install --ignore-scripts. It works. It also breaks bcrypt, canvas, esbuild, swc, prisma, and husky. Native addons won't compile. Platform binaries won't download. Your ORM won't generate its client.
So you maintain an allowlist, run npm rebuild by hand for each blessed package, and hope nothing new in your dependency tree needs a hook you forgot about. That's not a security strategy. That's a part-time job.
Same package.json, Different Intent
Here's the actual problem: "postinstall": "node install.js" looks identical whether it downloads the esbuild binary or steals your credentials. The hook value tells you nothing. You have to read the code it points to.
Dependency Guardian reads those files. It follows the trail past the scripts field in package.json into the code those hooks execute, and it knows the difference between a build tool compiling a native addon and a script exfiltrating your npm tokens.
esbuild runs node install.js in its postinstall to download the correct platform binary from its own CDN. Known publisher, known behavior. Dependency Guardian result: pass.
Now take a package with the same "postinstall": "node build.js" where build.js reads ~/.npmrc and POSTs the contents to an external server. Three independent signals fire: install hook execution, sensitive file access, outbound network call. Result: blocked.
Both entries look the same in package.json. The difference only shows up when you analyze what the code does.
Scan Before You Merge
The most dangerous moment is when dependencies change in a pull request. A malicious package enters your lockfile there, and once it's merged, every npm install from that point forward runs the payload.
Dependency Guardian catches it at that boundary. Add the GitHub App, and it scans PRs automatically when your lockfile changes. Results show up as a comment before anyone clicks merge.
npm install -g @westbayberry/dg
Or install the GitHub App and stop thinking about it. 1,000 scans per month on the free tier, all detectors included.