Skip to contentExploitQuest

Lesson 1 of 1 in The Packages You Did Not Choose

The Ones You Did Not Choose

You picked twelve dependencies. You installed nine hundred. The other eight hundred and eighty-eight are the interesting ones.

2 min read

Not yet reviewed

Every package you install runs with the same privileges as your application, and most of what you install is not what you chose. A dependency tree is code from hundreds of people you have never evaluated, arriving through twelve you have.

Lock the tree, then look at it

npm ci
npm audit --omit=dev
npm ls --all | wc -l
  1. Line 1ci installs exactly the lockfile. install may resolve something new, which means the build that passed and the build that shipped can differ — the property you most need not to have.
  2. Line 2Known vulnerabilities only. It is a floor, not a ceiling: a package can be malicious without anybody having filed anything.
  3. Line 3Read the number once. Most people are surprised, and the surprise is the point of this chapter.

Note

A lockfile is a security control, not a convenience. Without it "the version that was audited" and "the version that is running" are different questions with no reliable answer.

The install script

A package can run arbitrary code at install time. That code executes on your laptop and on your build machine, with your credentials, before any of it has been reviewed — which is why a compromised package is a compromised CI pipeline rather than a bug in production.

Take care

npm ci --ignore-scripts in CI wherever the build allows it, and treat a package that genuinely needs an install script as a decision rather than a default. Most do not need one.

Typosquats and the name you nearly typed

What was typed

cross-env.js
electon
lodahs

What was meant

cross-env
electron
lodash

All three were real packages that existed to be installed by mistake. The defence is not vigilance — it is that you add dependencies rarely and deliberately, so a new name in a diff is a thing worth a second look.

What this platform does

The dependency count is kept deliberately small, and the standard library is preferred to a package wherever it is close. That is not minimalism for its own sake: every dependency is a permanent commitment to somebody else's judgement, and the cheapest supply-chain control is having fewer suppliers.

Why is `npm ci` preferred to `npm install` in a build pipeline?

Tip

Before adding a dependency, look at three things: when it was last published, how many people maintain it, and how many packages it drags in. A one-function package with forty transitive dependencies is a worse deal than the twenty lines you were avoiding.