Safeguard
Software Supply Chain•

What does package-lock.json do and should you commit it?

package-lock.json pins the exact dependency tree npm installed. Commit it for apps, install with npm ci in CI, and review its diffs like code. Here is why.

Safeguard Security Team
4 min read

package-lock.json records the exact version, location and integrity hash of every package npm installed, so teammates, CI and production get an identical tree. Commit it for applications. Install from it with npm ci. Review changes to it in pull requests, because that file is where supply chain changes show up.

What is actually inside the file?

npm generates the lockfile whenever it changes node_modules or package.json. According to the npm docs, it describes the exact tree that was generated so later installs produce identical trees regardless of newer releases in between.

Each entry carries a resolved URL, a version and an integrity value. Since npm v7 the lockfile holds enough data to rebuild the full package tree without reading every package's own manifest, which also makes installs faster.

Your package.json says what you are willing to accept, for example a caret range that allows any compatible minor release. The lockfile says what you got. Without it, two installs a week apart can pull different code under the same manifest.

Why does the lockfile matter for security?

A range such as ^4.17.0 lets a new release flow into your build the moment it is published. If a maintainer account is hijacked and a malicious patch version ships, an unlocked install picks it up on the next run. A locked install keeps the version you already reviewed until someone changes the lockfile on purpose.

The integrity hash adds a second check. If the tarball behind a locked version changes, the hash no longer matches and the install fails.

That is a limit worth stating: a lockfile does not make a dependency safe. It makes your choice repeatable. If you locked a bad version, you keep getting the bad version.

npm install or npm ci?

The npm docs describe npm ci as the command for automated environments. It requires an existing lockfile, exits with an error if the lockfile and package.json disagree, removes any existing node_modules first, and never writes to the lockfile.

npm install can rewrite the lockfile. That is right on a developer laptop when you add a package, and wrong in CI, where a silent rewrite hides drift.

One detail from the docs trips people up: if you built the lockfile with flags that change the tree shape, such as legacy-peer-deps, you must pass the same settings to npm ci. Put them in a committed .npmrc so everyone agrees.

Should libraries commit it too?

Commit it, but know what it does. The docs state that package-lock.json cannot be published and is ignored anywhere but the project root. Consumers of your library resolve your declared ranges, not your lockfile. A separate file, npm-shrinkwrap.json, can be published, but npm does not recommend it except for things like CLI tools shipped as production packages.

For a library, the committed lockfile still gives your own CI a stable base. Test against newer ranges separately if you want to know how consumers will fare.

A constructed example of a lockfile diff

Imagine a pull request titled "fix typo in README" that also changes 40 lines in package-lock.json. One entry moved from a registry URL to a git URL, and an integrity hash disappeared. A reviewer who skims lockfiles would miss it. A reviewer who checks the resolved field would stop the merge. Unexplained changes to resolved sources are the pattern to catch.

How to check or apply this

  1. Confirm package-lock.json is committed and not listed in .gitignore.
  2. Use npm ci in every pipeline and Dockerfile build step.
  3. Fail the build if running npm install produces a lockfile diff.
  4. In reviews, scan the lockfile diff for new packages, changed resolved URLs and removed integrity fields.
  5. Update on a schedule, in a dedicated pull request, so changes are visible rather than mixed with features.
  6. Scan the locked tree for known vulnerabilities and check whether the flagged code is reachable from your app.

Safeguard reads lockfiles as part of SBOM and dependency scanning, and reachability analysis helps you separate vulnerable packages that your code calls from ones it never touches. The free scan at gold.safeguard.sh/scan accepts up to 100 dependencies in the browser.

Quick check

Does npm ci ever change package-lock.json?

No. The npm docs say it never writes to package.json or any lockfile. If the two disagree, it exits with an error.

Is a lockfile the same as a software bill of materials?

No. A lockfile is npm's install instruction. An SBOM is a standard inventory format that can include more context. You can generate an SBOM from a lockfile.

Sources

Last reviewed: 18 October 2026.

Never miss an update

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

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.