Open Source Security

npm Lockfile v3 Security Improvements

Lockfile v3 is more than a format bump. It quietly fixed a class of integrity bugs that plagued v1 and v2, and the difference matters more than most teams realize.

Nayan Dey
Senior Security Engineer
6 min read

I spent an afternoon in May 2023 staring at a package-lock.json diff that was nine thousand lines long. The branch's actual change was a single patch bump to axios. The lockfile had rewritten every resolved URL, flipped several integrity hashes between sha1 and sha512, and reordered dependencies so that the diff was, practically speaking, unreviewable. That is the world lockfile v1 left us with, and it is the reason lockfile v3 matters.

Npm 7, released in October 2020, introduced lockfile v2. Npm 9, released in October 2022, made lockfile v3 the default for new installs. The transition has been quieter than it should be. Teams upgrade npm and inherit the new format without realizing what actually changed underneath, and a surprising number of production pipelines still use --lockfile-version=1 because of a long-forgotten flag in a Dockerfile. If you're in that boat, here is the short history and the reason to catch up.

What v1 Actually Was

Lockfile v1 was a single-tree JSON representation of node_modules as it existed on the author's machine. It carried the resolved URL, the integrity string, and the nested dependency tree. It had three well-known security problems.

The first was that resolved URLs were not required to point at the configured registry. If I installed from registry.npmjs.org and you installed from a private Verdaccio mirror, our lockfiles would diverge. An attacker who could influence the resolved URL, through a malicious CI script for example, could redirect a future install to a hostile registry without changing the package name or version string. The 2021 research by Alex Birsan into dependency confusion highlighted this sharp edge directly.

The second was integrity drift. Early v1 lockfiles used sha1. Later versions used sha512. Mixed lockfiles, where some entries used each, were common. The npm CLI would accept either, which meant a tampered package could be introduced as long as the corresponding sha1 matched. Sha1 preimage attacks are not yet practical against a random blob, but collision-generating tools for sha1 shipped in 2017 (the shattered attack), and the defense-in-depth argument was clear.

The third, and most painful in day-to-day work, was the unreviewable diff. When the tree reorganized, every affected entry moved in the file. Security reviewers could not tell a legitimate change from a malicious injection.

What v2 Fixed

Lockfile v2, shipped with npm 7, introduced the packages object alongside the legacy dependencies tree. The packages object was keyed by install path, so each entry had a stable location in the file. Diffs suddenly became readable. Npm still wrote the legacy dependencies tree for backward compatibility with older tooling, so the file doubled in size, but the security benefit was real: a reviewer could see that only one line had changed when only one line had changed.

V2 also tightened integrity requirements. Sha512 became the default for new entries, and the CLI warned when installing a package whose lockfile entry carried only sha1.

What v3 Actually Removes

Lockfile v3, the default since npm 9, drops the legacy dependencies tree entirely. The file is now a clean packages object keyed by path. This sounds cosmetic, but it closes a subtle class of attacks: in v2, a malicious actor with write access to the lockfile could modify the dependencies tree while leaving the packages object intact. If the consuming tooling, such as a security scanner, looked at only one of the two trees, the two representations could disagree. V3 forces a single source of truth.

V3 also standardizes on sha512 integrity and rejects sha1 outright. In npm 9 and later, running npm install against a v3 lockfile with a sha1 entry will re-resolve and rewrite the entry to sha512, or fail if the upstream tarball is no longer available in a verifiable form.

The integrity Field Is Now Load-Bearing

One behavior change that catches teams off guard: if you commit a v3 lockfile and a tarball on the registry is re-published (which npm allows under specific conditions, such as unpublishing and republishing within the 72-hour window before npm's 2016 policy change, or through legitimate republishes of un-immutable packages), the integrity hash in your lockfile will no longer match and npm ci will fail hard. This is the correct behavior. But it means operational teams need to be prepared for install failures that are genuine integrity mismatches, not a transient network issue.

I saw this trigger in June 2023 when the node-ipc maintainers re-signed several versions after the RIAEvangelist protest-ware incident from March 2022 had lingering package-level effects. Teams that had pinned older versions with v3 lockfiles got clean EINTEGRITY failures in CI. The correct response was to verify the new hash against the registry, understand why it had changed, and commit the updated lockfile. The wrong response, which I saw more than once, was to add --no-integrity to the install command.

Provenance And The Sigstore Link

Starting in April 2023, npm added support for package provenance attestations via Sigstore. Provenance attestations are separate from the lockfile, but v3 makes them usable: because v3 lockfiles are authoritative and path-stable, the npm audit signatures command can walk the lockfile and verify each package's signature and provenance statement in one pass. Against a v1 or v2 lockfile, the same command had to cross-reference two trees and was, in my testing on a large monorepo in July 2023, roughly 3x slower.

Migration Advice

If your repo still emits v1 or v2 lockfiles, the upgrade is npm install --lockfile-version=3 followed by a commit. Review the diff carefully the first time; v3 removes the legacy tree and your file will shrink by roughly 30 to 50 percent, which looks alarming until you understand it. Pin the version in .npmrc with lockfile-version=3 so future installs don't silently regress.

For teams on CI systems with aggressive caching, verify that the Docker layer or GitHub Actions cache key includes the npm version. I've seen otherwise-careful pipelines keep writing v2 lockfiles for months because the build container was pinned to npm 8.

How Safeguard Helps

Safeguard validates lockfile versions during scan and flags repos that still emit v1 or v2 formats, since those repos carry known integrity-reviewability weaknesses. When we ingest a v3 lockfile, we compare the committed integrity hashes against the live registry and surface any mismatch as a supply-chain finding, not a generic vulnerability. For teams adopting Sigstore provenance, Safeguard's scanner walks the v3 packages tree to verify attestations in the same pass as SBOM generation, so you get one report instead of three. If you want a policy gate that blocks merges on lockfile downgrade or unverified provenance, Safeguard enforces it across GitHub, GitLab, and Bitbucket.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.