Pin your npm/yarn dependencies
Today, the open-source maintainer Marak intentionally bricked two popular JavaScript libraries (with ~25 total million weekly downloads) in such a way that they broke dependents software. There's nothing that npm or any other public registry could've done to have prevented this; the release was indistinguishable from a regular update, unless you looked at the code. This is not the first time an npm dependency has gone rogue and caused havoc due to supply chain problems, and it certainly won't be the last. And so the defensive role falls to you, the developer.
Pinning dependencies
My recommendation is to pin your dependencies. If you're unfamiliar with what this means, you should first be familiar with semantic versioning. In short, pinning dependencies means the exact version specified will be installed, rather than a dependency matching the range criteria. Here's an example:
To automatically accomplish this in your projects, you can add save-exact=true
to a .npmrc
file, or use --save-exact
when adding the dependency via npm (or --exact
via yarn).
If you do this, I also recommend some sort of dependency management so you stay up-to-date. I personally use the renovate bot for my GitHub projects.
What about lockfiles?
You may have noticed npm and yarn generate package-lock.json
and yarn.lock
when you run npm i
and yarn
, respectively. The lockfiles allow every version of every sub-dependency to be pinned, while modifying the package.json only guarantees we pin the immediate dependency (which may not pin its own dependencies).
One problem with relying on lockfiles is that they and their semantics are confusing. In my experience, many new-to-npm users expect npm i
to function like npm ci
, and are unaware of the differences. npm i
can update package-lock.json
, whereas npm ci
will only read from it.
Tip: You almost always want to use
npm ci
Meanwhile, with yarn, yarn install
will install the version in the yarn.lock, regardless of the version in the package.json. Good for security? Sure. Good for the developer experience? Not so much. You need to use yarn upgrade <package>
in order to fetch the version in the package.json if it's more recent than specified in the yarn.lock. It's now far more difficult to determine when something is updated. If you've ever found yourself staring at a yarn.lock diff you'll know what I mean.
Lockfiles are good tools, but can be difficult and unintuitive to work with.
In conclusion
Use lockfiles for pinning transitive dependencies, but have your package.json be the source of truth. Pin your dependencies so you have a human-friendly, readable method to audit your dependencies and ensure a specific version is installed. Automate some process to make sure you remain up-to-date.
If you disagree or have comments, feel free to email me or reach out on Twitter.