Your npm install Is More Dangerous Than You Think — A Practical Dependency Safety Checklist
We type this command almost without thinking:
npm install
A few seconds later, hundreds — sometimes thousands — of packages appear inside node_modules.
Then we start coding.
But there is something developers often forget:
Installing one npm package does not mean trusting only one package.
You may also be trusting:
- its dependencies
- dependencies of those dependencies
- package maintainers
- install scripts
- downloaded artifacts
- future package updates
- your lockfile
- your package registry
Most of the time, everything works perfectly.
But when something goes wrong in the software supply chain, npm install can become a surprisingly powerful attack surface.
So before blindly installing the next package, here is the checklist I think developers should follow.
1. Check the package before installing it
Suppose you need a date library.
Don't immediately do:
npm install some-random-date-package
Spend 30 seconds checking it first.
Look at:
- npm package page
- GitHub repository
- latest release
- maintenance activity
- open issues
- number of dependents
- package documentation
- who maintains it
The important question isn't:
Does this package have a lot of downloads?
The better question is:
Do I actually trust this project enough to execute its code inside my application?
A popular package can still become compromised.
And an unpopular package is not automatically dangerous.
You need context.
2. Check the package name carefully
This sounds obvious.
But dependency attacks don't always require sophisticated exploits.
Sometimes they rely on developers installing the wrong package.
Imagine you wanted:
some-package
but accidentally installed:
some-pakage
One missing letter can matter.
Before installing unfamiliar dependencies, check the exact:
package name
publisher
repository
Especially when copying commands from random tutorials, Stack Overflow answers, GitHub issues, or AI-generated code.
3. Understand that packages can execute scripts
This is one of the most important things to understand.
npm supports lifecycle scripts such as:
preinstall
install
postinstall
prepare
These scripts can execute as part of the installation lifecycle. npm documents these lifecycle hooks directly.
For legitimate packages, these scripts might:
- compile native code
- download binaries
- generate files
- prepare dependencies
But from a security perspective, you should remember:
Installing a package may involve executing code, not simply downloading JavaScript files.
For suspicious or unfamiliar projects, you can investigate package scripts before giving them full trust.
Modern npm versions also provide controls around which dependency install scripts are permitted.
4. Commit your package-lock.json
Your lockfile is not random noise that Git generated.
It matters.
package-lock.json records the exact dependency tree npm resolved.
npm explicitly recommends committing it because it helps developers, deployments, and CI install the same dependency tree.
Without a lockfile, two developers may install slightly different versions depending on when they run:
npm install
So this:
package.json
package-lock.json
should normally live together in your repository.
And if a pull request contains a massive unexpected lockfile change?
Don't automatically approve it.
Look at what changed.
5. Prefer npm ci in CI/CD
For automated environments, I generally don't want my dependency tree quietly changing.
That's where:
npm ci
becomes useful.
npm documents an important difference:
npm ci requires a lockfile and will fail when package.json and the lockfile disagree instead of rewriting the lockfile. It also performs a clean install.
That's exactly what you usually want in CI.
Think of it like this:
Developer machine
npm install
CI / deployment
npm ci
Not a universal rule, but a good default.
6. Actually look at dependency changes in pull requests
Imagine someone changes:
"dependencies": {
"express": "...",
"new-library": "..."
}
Most code reviews focus on the application code.
The dependency change gets ignored.
That is backwards.
A new dependency can potentially introduce far more code than the developer wrote in the PR.
GitHub's Dependency Review can show:
- added dependencies
- removed dependencies
- version changes
- known vulnerabilities
- package usage information
It can also be enforced in CI so vulnerable dependencies introduced by a pull request can cause the check to fail.
Dependency changes deserve code review too.
7. Run npm audit — but don't treat it like magic
You probably know:
npm audit
It's useful.
But don't think:
0 vulnerabilities
means:
100% secure
Security scanners mostly help you detect known problems.
They cannot guarantee that:
- a maintainer isn't malicious
- a package hasn't been compromised in another way
- your own implementation is secure
- a vulnerability hasn't been discovered yet
So use auditing as one layer.
Not the entire security strategy.
8. Consider package signature verification
Here's something many npm developers still don't use.
npm supports:
npm audit signatures
According to npm's documentation, this can verify registry signatures and provenance attestations for downloaded packages when supported.
That doesn't mean every developer needs to run it manually before every installation.
But for higher-security environments, package integrity and provenance are becoming increasingly important concepts.
We shouldn't only ask:
What package did I download?
We should increasingly ask:
Where did this package come from, and can I verify it?
9. Don't install a dependency for five lines of code
This is partly a security problem and partly an engineering problem.
Imagine you need:
capitalize a string
Do you really need another dependency?
Every dependency adds something to maintain.
Potentially:
updates
breaking changes
licenses
vulnerabilities
transitive dependencies
supply-chain risk
I'm not saying:
Never use packages.
That would be ridiculous.
The npm ecosystem exists because reusing good software is incredibly valuable.
But there should be a small mental check:
Is adding another dependency worth it?
Sometimes the correct answer is absolutely yes.
Sometimes the function takes five lines.
10. Be careful with abandoned dependencies
A dependency doesn't need to be malicious to become a problem.
Sometimes it simply stops being maintained.
Check:
Last update: 4 years ago
Issues: 237
Pull requests: 48
Maintainers: 1
That doesn't automatically mean you shouldn't use it.
Stable software doesn't need commits every Tuesday.
But if the package sits in a security-sensitive part of your application, maintenance status matters.
Especially for things like:
- authentication
- encryption
- file uploads
- parsers
- networking
- database drivers
11. Don't blindly accept dependency updates
There's another dangerous habit:
Dependabot opened 17 PRs.
Merge.
Merge.
Merge.
Merge.
Updating dependencies is important.
Blindly updating them isn't ideal either.
An update can introduce:
breaking behavior
new dependencies
changed build scripts
new permissions
new vulnerabilities
Review what changed.
For important dependencies, read the release notes.
A green CI check is helpful, but it doesn't understand your entire production environment.
12. Protect your CI environment
Dependency installation becomes much more serious inside CI.
Why?
Because CI might have access to:
NPM_TOKEN
GitHub tokens
cloud credentials
deployment secrets
SSH keys
database URLs
Now imagine running untrusted install scripts in the same environment.
That's a much bigger risk than installing something on an empty test machine.
Good CI design should follow least privilege.
A build that only needs to compile code probably doesn't also need production database credentials.
My practical npm dependency checklist
Before adding an unfamiliar dependency, I now think through something like this:
[ ] Did I verify the exact package name?
[ ] Is the repository legitimate?
[ ] Is the project actively maintained?
[ ] Do I understand why this dependency is needed?
[ ] Does it contain install scripts?
[ ] How many transitive dependencies does it bring?
[ ] Did package-lock.json change unexpectedly?
[ ] Does npm audit report known vulnerabilities?
[ ] Is CI using npm ci?
[ ] Am I exposing unnecessary secrets during installation?
[ ] Do I really need this dependency?
You don't need to spend 30 minutes investigating every package.
But 30 seconds of thought is better than:
npm install random-package
and hoping for the best.
The bigger lesson
Modern applications are not just the code we write.
They're more like:
Your code
+
framework
+
libraries
+
transitive dependencies
+
build tools
+
GitHub Actions
+
cloud infrastructure
A small application can depend on code written by hundreds or thousands of people you've never met.
That's one of open source's greatest strengths.
It's also why dependency security matters.
So no, you shouldn't become afraid of:
npm install
But you should stop thinking of it as:
Download some code.
Think of it as:
Add someone else's software to my trust boundary.
That tiny mindset change can make you a much safer developer.
How carefully do you review a package before installing it?
I'm curious whether your team has a dependency security checklist or mostly relies on automated tools.
Top comments (1)
Hi Robert, this is a great reframe, "trust boundary" feels like exactly the right way to describe what an install actually does. I liked it enough that I introduced it to readers here in Japan, writing up a full piece (in Japanese, apologies to non-readers) that walks through each item against my own tool, so thanks for giving me something concrete enough to build on rather than just nod along with.
Of the 12 items, 5 turned out to be things my tool (a local Mac/Linux security agent I build and run solo) already covers in some form: typosquatting detection on package names via Levenshtein distance against a popular-package list, splitting npm install/pnpm install into an --ignore-scripts pass followed by re-running only the skipped lifecycle scripts inside a bwrap sandbox that masks out ~/.ssh, ~/.aws, ~/.kube, and ~/.npmrc, SHA-256 baselining of lockfile contents to catch unexpected drift, a CVE scan against OSV.dev across several language ecosystems (adjacent to npm audit, same "known threats only" ceiling you called out), and a thin wrapper around npm audit signatures rather than rolling my own verification.
4 items (npm ci in CI, reviewing dependency diffs in PRs, not rubber-stamping updates, restricting CI secret access) are squarely outside what an endpoint agent should be reaching into. That's CI/CD and PR-review territory, and I'd rather leave it to Dependabot, Renovate, and branch protection than stretch the product's shape to cover it.
2 items (checking repo/maintenance status before install, watching for abandoned dependencies) I'm holding off on for a different reason: they'd require asking an external service about the specific package name being installed, which cuts against the receive-only, no-identifiers-out principle the rest of the tool follows. If I ever add this, it'll need the same opt-in-plus-confirm-each-time gate I already use for signature verification.
The last one (avoid unnecessary dependencies) is the one item that just isn't a good fit for automated judgment, "unnecessary" depends on project context in a way a tool can't reliably call.
Mapping it out like this was genuinely useful, it drew a much clearer line between what a tool can close and what has to stay a human habit than I expected going in.