Trust is earned, not given

A different perspective

2019-11-19 · Projects

Node.js, part 8: getting dependencies under control — lockfiles, npm ci, and audit

Part 8from the Node.js series · 24 parts in all

An application's dependency tree is code you did not write, did not review, and ship to production on every deploy. Node's ecosystem is also the largest one there is. Part 8 is the small set of practices that keep the tree from turning into an incident: an exact lockfile, a reproducible install, and a policy for version ranges.

Lockfile: commit it, and never hand-edit it

package-lock.json records the exact resolved version and integrity hash of every package, including transitive ones. It is the difference between "we tested this build" and "we tested a build":

npm install            # resolves, MAY update the lockfile
npm ci                 # installs EXACTLY the lockfile, deletes node_modules first

npm ci is the one to use in CI and in production images. It cannot silently upgrade anything, it fails loudly when package.json and the lockfile disagree (which is how a forgotten commit gets caught), and it is faster because there is no resolution work. The corollary: a lockfile that is not committed is not a lockfile.

Ranges: what you actually asked for

Semver ranges are a trust statement about other people's discipline:

The subtlety that causes real outages: ^0.x ranges are not minor-safe in the same way, because a 0.x minor version is allowed to break. A dependency at ^0.9.0 can ship a breaking change as 0.10.0.

audit and the judgement call

npm audit                  # findings, grouped by severity
npm audit --production     # ignore devDependencies - usually what matters
npm audit fix              # applies changes allowed by your ranges

Treat the output as a queue with judgement, not a gate. A prototype-pollution advisory in a package you call with a hard-coded literal is not the same as a path-traversal in an upload handler, even when the scanner scores them the same. What deserves an immediate fix: anything reachable from untrusted input, anything in the request path, and anything with a known exploit in the wild. Next: Node 14 and the syntax that quietly deleted a lot of defensive code.