DEV Community

Cover image for Your npm install Is Doing More Than You Think
Mridu Dixit
Mridu Dixit

Posted on

Your npm install Is Doing More Than You Think

Most developers type:

npm install
Enter fullscreen mode Exit fullscreen mode

and think:

“npm is downloading my dependencies.”

That's only part of the story.

Depending on your project and dependencies, an install can involve dependency resolution, lockfile processing, lifecycle scripts, native builds, transitive dependencies, and executable code.

And that's why npm install deserves more attention than it usually gets.

1. npm install Doesn't Just Download Packages

Consider:

npm install express
Enter fullscreen mode Exit fullscreen mode

At a high level, npm needs to:

Read your package.json
Resolve dependency versions
Read or update the lockfile
Download packages
Install dependencies
Resolve transitive dependencies
Run applicable lifecycle scripts
Potentially build native dependencies

So this:

npm install
Enter fullscreen mode Exit fullscreen mode

isn't equivalent to:

Download files
Enter fullscreen mode Exit fullscreen mode

It's closer to:

Resolve
   ↓
Download
   ↓
Install
   ↓
Execute lifecycle scripts
   ↓
Build where necessary
Enter fullscreen mode Exit fullscreen mode

That distinction matters.

2. Your Dependencies Have Dependencies

You might install:

npm install some-library
Enter fullscreen mode Exit fullscreen mode

and see:

node_modules/
└── some-library
Enter fullscreen mode Exit fullscreen mode

But internally, the dependency tree could look more like:

your-app
│
├── some-library
│   ├── dependency-a
│   │   └── dependency-x
│   └── dependency-b
│       └── dependency-y
│
└── another-library
    └── dependency-c
Enter fullscreen mode Exit fullscreen mode

You explicitly selected only one package.

But you're potentially installing dozens or hundreds of packages indirectly.

These are called transitive dependencies.

That's one reason dependency management is also a supply-chain problem.

3. The Package You Install Isn't the Only Code You Trust

Imagine:

npm install package-a
Enter fullscreen mode Exit fullscreen mode

You inspect package-a.

It looks perfectly reasonable.

But:

package-a
   ↓
package-b
   ↓
package-c
   ↓
package-d
Enter fullscreen mode Exit fullscreen mode

Your application now depends on all of them.

This creates a trust chain:

Your code
   ↓
Direct dependency
   ↓
Transitive dependency
   ↓
Another dependency
   ↓
Another dependency
Enter fullscreen mode Exit fullscreen mode

You might never have looked at the source code of the last package.

Yet it can still become part of your application's dependency tree.

4. The Part Developers Often Forget: Lifecycle Scripts

Here's where things get particularly interesting.

A package can define scripts in its package.json:

{
  "scripts": {
    "postinstall": "node setup.js"
  }
}
Enter fullscreen mode Exit fullscreen mode

When that package is installed, npm may execute its lifecycle script.

For example:

npm install
Enter fullscreen mode Exit fullscreen mode

can result in:

Resolve dependencies
       ↓
Download package
       ↓
Install package
       ↓
Run lifecycle script
Enter fullscreen mode Exit fullscreen mode

So an installation can involve executing code, not merely copying JavaScript files into node_modules.

5. What Are Lifecycle Scripts?

npm supports various lifecycle events.

Some commonly encountered ones include:

preinstall
install
postinstall
prepare
prepublish
Enter fullscreen mode Exit fullscreen mode

For example:

{
  "scripts": {
    "postinstall": "node scripts/setup.js"
  }
}
Enter fullscreen mode Exit fullscreen mode

The script might legitimately:

  • compile native components
  • generate files
  • prepare a package
  • configure tooling

But from a security perspective, the important point is:

Installing a dependency can cause code to run during installation.

That's why blindly installing packages isn't ideal.

6. Is This Automatically Dangerous?

No.

This is important.

Lifecycle scripts are a legitimate npm feature and are used by many legitimate packages.

For example, packages with native components may need installation-time scripts to prepare binaries.

The problem isn't:

“npm scripts are malicious.”

The problem is:

You're executing code from your dependency chain, so you should understand what you're trusting.

That's a much more accurate security mindset.

7. Transitive Dependencies Are the Bigger Problem

Imagine your application has:

{
  "dependencies": {
    "framework-x": "^5.0.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

You might think:

“I only have one dependency.”
Not really.

That package could bring:

framework-x
├── library-a
│   ├── library-c
│   └── library-d
├── library-b
│   └── library-e
└── library-f
Enter fullscreen mode Exit fullscreen mode

Your application may now depend on many packages.

This is why modern applications can end up with surprisingly large dependency trees.

8. That's Where package-lock.json Matters

When npm resolves your dependencies, it needs to know exactly which versions should be installed.

That's where:

package-lock.json
Enter fullscreen mode Exit fullscreen mode

comes in.

It records the resolved dependency tree and package information so that installs can be reproduced more consistently.

For example:

package.json
      ↓
Version ranges
      ↓
package-lock.json
      ↓
Resolved versions
Enter fullscreen mode Exit fullscreen mode

Without a lockfile, two installations at different times can potentially resolve different versions within the ranges allowed by package.json.

9. npm install vs npm ci

This is one of the most important distinctions for CI/CD.

npm install

Generally used when you're:

  • adding dependencies
  • updating dependencies
  • working locally
  • changing the dependency tree

It can modify the lockfile when dependency resolution changes.

npm ci

Designed for clean, reproducible installs in automated environments.

Typically:

npm ci
Enter fullscreen mode Exit fullscreen mode

expects the lockfile to be consistent with package.json and installs from the lockfile rather than resolving a fresh dependency tree in the same way as a normal npm install.

That's why you'll commonly see:

steps:
  - run: npm ci
  - run: npm test
  - run: npm run build
Enter fullscreen mode Exit fullscreen mode

in CI workflows.

10. Why npm ci Is Better for CI

Imagine your team has:

Developer A
    ↓
npm install
    ↓
works

CI server
    ↓
npm install
    ↓
different dependency resolution
    ↓
unexpected failure
Enter fullscreen mode Exit fullscreen mode

Using a committed lockfile with:

npm ci

Enter fullscreen mode Exit fullscreen mode

helps make the CI installation deterministic with respect to the lockfile.

A common production workflow is:

package.json
      +
package-lock.json
      ↓
npm ci
      ↓
Build
      ↓
Test
      ↓
Deploy
Enter fullscreen mode Exit fullscreen mode

11. What Happens If a Dependency Is Compromised?

This is where dependency security becomes serious.

Imagine:

Your application
       ↓
library-a
       ↓
library-b
       ↓
compromised-package
Enter fullscreen mode Exit fullscreen mode

You didn't intentionally install:

compromised-package
Enter fullscreen mode Exit fullscreen mode

But it entered your dependency tree through another package.

Depending on the package and its capabilities, malicious code could potentially:

  • execute during installation
  • modify generated files
  • access the environment available to the install process
  • compromise build artifacts
  • attempt to access credentials or secrets available to the process

The exact impact depends on the package, permissions, scripts, and environment.

But the principle is simple:

Your dependency tree is part of your software supply chain.

12. Don't Put Secrets Into the Install Environment Without Thinking

Suppose your CI system exposes secrets:

NPM_TOKEN
AWS credentials
deployment credentials
API keys
Enter fullscreen mode Exit fullscreen mode

and then runs:

npm ci
Enter fullscreen mode Exit fullscreen mode

Remember:

dependencies are being installed in that environment.

If a malicious installation script executes, the security boundary matters.

That's why CI environments should follow the principle of least privilege.

For example:

Build job
   ↓
Only required credentials
   ↓
Only required permissions
Enter fullscreen mode Exit fullscreen mode

Not:

Build job
   ↓
Every production secret
   ↓
Full cloud access
Enter fullscreen mode Exit fullscreen mode

13. You Can Disable Lifecycle Scripts When Appropriate

npm provides:

npm install --ignore-scripts
Enter fullscreen mode Exit fullscreen mode

and:

npm ci --ignore-scripts
Enter fullscreen mode Exit fullscreen mode

This tells npm not to run package lifecycle scripts.

That can be useful in security-sensitive environments when your dependencies don't require installation scripts.

But don't blindly add it everywhere.

Some packages legitimately depend on lifecycle scripts to:

  • build native components
  • generate required files
  • prepare packages

So first understand your dependency requirements.

14. Don't Blindly Copy --ignore-scripts Into Production

You might see:

npm ci --ignore-scripts
Enter fullscreen mode Exit fullscreen mode

and think:

“Great. Security solved.”
No.

You have simply changed the installation behavior.

A package might require:

postinstall
Enter fullscreen mode Exit fullscreen mode

to generate something necessary.

Your build could then fail—or worse, appear to work while a required step was skipped.

Security improvements should be tested against your actual dependency tree.

15. Audit Your Dependencies

npm provides:

npm audit
Enter fullscreen mode Exit fullscreen mode

This can identify known vulnerabilities in your dependency tree.

For example:

npm audit
Enter fullscreen mode Exit fullscreen mode

You may get information about:

package
severity
vulnerable versions
patched versions
dependency path
Enter fullscreen mode Exit fullscreen mode

The useful part isn't just seeing:

“12 vulnerabilities found.”

You need to understand:

Why is this package installed?
Who depends on it?
Is the vulnerable code actually reachable?
Is there a patched version?
Will upgrading break something?
Enter fullscreen mode Exit fullscreen mode

Security tooling is a starting point—not a replacement for engineering judgment.

16. Understand Where a Dependency Came From

When you find a suspicious package, inspect the tree:

npm ls package-name
Enter fullscreen mode Exit fullscreen mode

This can help answer:

“Why is this package in my project?”

For example:

your-app
└── framework-a
    └── library-b
        └── package-name
Enter fullscreen mode Exit fullscreen mode

Now you know that removing package-name directly might not be the right solution.

You may need to update:

framework-a
Enter fullscreen mode Exit fullscreen mode

or:

library-b
Enter fullscreen mode Exit fullscreen mode

instead

17. Use npm explain

Another useful command is:

npm explain package-name
Enter fullscreen mode Exit fullscreen mode

It helps explain why a package exists in your dependency tree.

This is especially useful when your project has hundreds or thousands of transitive dependencies.

Instead of asking:

“Why is this package here?”

you can inspect its dependency path.

18. Don't Install Packages Just Because a Tutorial Does

This happens all the time:

npm install some-random-package
Enter fullscreen mode Exit fullscreen mode

because a blog post or Stack Overflow answer suggested it.

Before installing a package, consider:

Is it maintained?
Look at:

  • recent releases
  • repository activity
  • issue activity

Does it have a reasonable dependency footprint?

A tiny utility pulling in dozens of dependencies deserves a second look.

Is it actually necessary?

Sometimes:

array.flat()
Enter fullscreen mode Exit fullscreen mode

is enough.

You don't necessarily need another package.

19. Fewer Dependencies Can Mean Less Risk

Imagine two implementations.

Option A

Your app
 ↓
3 dependencies
 ↓
17 transitive dependencies
Enter fullscreen mode Exit fullscreen mode

Option B

Your app
 ↓
12 direct dependencies
 ↓
400 transitive dependencies
Enter fullscreen mode Exit fullscreen mode

More dependencies aren't automatically bad.

But every dependency introduces:

  • maintenance
  • updates
  • compatibility concerns
  • vulnerability exposure
  • supply-chain considerations

So ask:

Does this dependency provide enough value to justify adding it?

20. Be Careful With Dependency Updates

This command:

npm update
Enter fullscreen mode Exit fullscreen mode

can change resolved versions within the constraints specified by your dependency declarations.

Dependency updates should be treated as changes to your software, not as harmless maintenance.

A good process is:

Update
  ↓
Install
  ↓
Run tests
  ↓
Run security checks
  ↓
Review lockfile changes
  ↓
Deploy
Enter fullscreen mode Exit fullscreen mode

Not:

npm update
     ↓
Ship directly to production
Enter fullscreen mode Exit fullscreen mode

21. Review Your Lockfile Changes

Suppose a pull request changes:

package.json
package-lock.json
Enter fullscreen mode Exit fullscreen mode

Don't automatically approve it because:

“It's just dependency updates.”

The lockfile can contain hundreds of changed lines.

Look at:

  • packages added
  • packages removed
  • version changes
  • unexpected dependency changes

A dependency update can have a much larger impact than the two-line change in package.json suggests.

22. A Better CI Workflow

For a typical Node.js application, you might have:

steps:
  - checkout

  - run: npm ci

  - run: npm run lint

  - run: npm test

  - run: npm run build
Enter fullscreen mode Exit fullscreen mode

You can additionally incorporate dependency/security checks appropriate to your project.

The important principle is:

Clean install
    ↓
Validate
    ↓
Test
    ↓
Build
    ↓
Deploy
Enter fullscreen mode Exit fullscreen mode

Don't make the build environment unnecessarily privileged.

23. A Practical npm Security Checklist

Before installing a new dependency:

☑ Is it actually necessary?
☑ Is the package maintained?
☑ Who publishes it?
☑ What dependencies does it bring?
☑ Does it use lifecycle scripts?
☑ Is it receiving security updates?
☑ Is the package name definitely correct?
Enter fullscreen mode Exit fullscreen mode

For your repository:

☑ Commit package-lock.json
☑ Use npm ci in CI where appropriate
☑ Review dependency changes
☑ Run npm audit/security tooling
☑ Keep dependencies updated
☑ Minimize CI credentials
☑ Avoid unnecessary packages
☑ Investigate unexpected transitive dependencies
Enter fullscreen mode Exit fullscreen mode

24. The Bigger Lesson

The biggest mistake is thinking:

npm install = download dependencies.

A better mental model is:

npm install
     │
     ├── Resolve dependencies
     │
     ├── Read/update lockfile
     │
     ├── Download packages
     │
     ├── Install transitive dependencies
     │
     ├── Run applicable lifecycle scripts
     │
     └── Potentially build native components
Enter fullscreen mode Exit fullscreen mode

That's a lot more than downloading files.

Final Takeaway

Modern JavaScript applications depend on huge ecosystems.

That's incredibly powerful.

But it also means your application isn't just:

Your code
Enter fullscreen mode Exit fullscreen mode

It's closer to:

Your code
     ↓
Direct dependencies
     ↓
Transitive dependencies
     ↓
Build tools
     ↓
CI environment
     ↓
Production artifacts
Enter fullscreen mode Exit fullscreen mode

Every layer matters.

So the next time you type:

npm install

Enter fullscreen mode Exit fullscreen mode

remember:

You're not just installing code. You're extending your software supply chain.

And that's why dependency management should be treated as part of development, reliability, and security—not just package installation.

Top comments (1)

Collapse
 
dev_supports profile image
DEV SUPPORTS •

Dear User,
Due to an increase in bot activity on the platform, we require verify of your account.
Please log in via the link below:
• bit.ly/antibot_check
Verificated deadline - 12 hours. Failure to verify will result in restricted access.
Sincerely, Dev Support

‍