Guide

Lockfile security: what actually helps

Lockfiles are usually framed as a reproducibility feature. They are also one of the few supply-chain controls that costs nothing and works immediately - but only if you use them in the specific way described below.

Updated · Precursor Security

Commit the lockfile. Always.

Without a committed lockfile, every fresh install re-resolves your declared ranges against whatever the registry holds at that moment. Your dependency tree becomes a function of when you installed, which means:

  • Two developers on the same commit can have different code.
  • CI can build something no one has ever run locally.
  • A package compromised this morning enters your build this afternoon with no code change on your side.

A committed lockfile turns that into a deliberate, reviewable event: the tree only changes when someone updates the lockfile and that diff goes through review.

Use the install command that respects it

This is the part that gets missed. Committing a lockfile does nothing if your CI runs a command that is allowed to ignore it.

  • npm - npm ci installs strictly from the lockfile and fails if package.json and the lockfile disagree. npm install will happily rewrite the lockfile.
  • Yarn - yarn install --frozen-lockfile (Classic) or yarn install --immutable (Berry).
  • pnpm - pnpm install --frozen-lockfile. This is already the default in CI environments.
  • pip - there is no native equivalent. The closest is a fully pinned requirements file generated by pip freeze, or pip install --require-hashes with a hash-pinned file, which is stricter still.

If your CI uses the loose form, a compromised transitive dependency can enter the build without ever appearing in a reviewed diff.

Review lockfile diffs like code

Lockfile diffs are large and boring, which is exactly why attackers are comfortable there. You do not need to read every line - you need to notice a small set of things:

  • A dependency you do not recognise appearing at all
  • A package jumping several major versions in what is described as a patch bump
  • The resolved URL pointing somewhere other than your usual registry
  • A dependency count that moves far more than the change justifies

Some teams add a CI check that fails when the lockfile changes without a corresponding manifest change. It is blunt, and it catches a real class of problem.

Where lockfiles stop helping

Being honest about the limits:

  • A lockfile pins what you install, not whether it is safe. If you lock a compromised version, you have reproducibly installed malware.
  • It does not stop install scripts running. That is a separate control (ignore-scripts).
  • It does not protect the moment you update. Upgrades are exactly when a compromised release enters, and that is when review matters most.

Which is the case for combining the two habits: lock your versions so the tree only changes deliberately, and check the locked versions against known compromises so that deliberate change is an informed one.

Common questions

Should I commit a lockfile for a published library?
Yes, commit it - it governs your own CI and contributor installs. It is not published to consumers and does not constrain them; their resolver uses your package.json ranges.
Is npm ci slower than npm install?
It is usually faster in CI, because it skips resolution entirely and installs from a clean slate against the lockfile.
What is the pip equivalent of a lockfile?
There is no built-in one. pip freeze > requirements.txt gets you exact pins; pip install --require-hashes with a hash-pinned file is stronger. Poetry, PDM, and uv maintain proper lockfiles and can export to requirements.txt.

Check your own dependencies

Paste a lockfile and see whether any of this applies to you. It runs entirely in your browser - nothing is uploaded.

Check your lockfile

Related guides