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 ciinstalls strictly from the lockfile and fails if package.json and the lockfile disagree.npm installwill happily rewrite the lockfile. - Yarn -
yarn install --frozen-lockfile(Classic) oryarn 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, orpip install --require-hasheswith 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
resolvedURL 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 cislower thannpm 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.txtgets you exact pins;pip install --require-hasheswith a hash-pinned file is stronger. Poetry, PDM, and uv maintain proper lockfiles and can export to requirements.txt.