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:
^1.4.2— any1.xat or above1.4.2. The default, and it will pick up new features and bug fixes without review.~1.4.2— patch releases only. Meaningfully narrower.1.4.2— exact. Right for applications, and mostly for tools you must reproduce.
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.