node_modules/.package-lock.json is a hidden lockfile npm 7 and later keeps so it does not have to re-read the whole node_modules tree. It describes what is installed on disk. The committed package-lock.json describes what should be installed. Scan the committed one for review, and treat the hidden one as a local cache.
The hidden lockfile
The npm documentation explains it. To avoid processing node_modules repeatedly, npm uses a hidden lockfile at node_modules/.package-lock.json. It is used in place of reading the entire hierarchy only if three conditions hold: every package folder it references exists, no package folder exists that is not listed in it, and its modified time is at least as recent as all the folders it references. If another tool changes the tree, npm detects it and ignores the hidden file.
The docs add a warning with security relevance. If you manually change the contents of a package, for example by adding a file inside node_modules/foo/lib, the folder's modified time does not change. If you are manually editing files in node_modules, the docs advise deleting node_modules/.package-lock.json. In other words, the hidden lockfile can be out of step with the real files if someone edited them in place.
The flags on each entry
The package-lock.json documentation lists fields on each entry in the packages object:
| Field | Meaning, as documented |
|---|---|
| dev | Strictly part of the devDependencies tree |
| optional | Strictly part of the optionalDependencies tree |
| devOptional | Both a dev dependency and an optional dependency of a non-dev dependency |
| inBundle | A bundled dependency |
| hasInstallScript | Has a preinstall, install or postinstall script |
| link | A symbolic link, with no other fields since the target is listed separately |
| resolved | Where the package was actually resolved from: a tarball URL, a git URL with commit, or a link target |
| integrity | A SHA-512 or SHA-1 integrity string for the unpacked artifact |
How to use the flags in a review
Scope. The dev flag tells you whether a package ships in your production install. A flagged vulnerability in a dev only package has a different risk profile from one in the runtime tree, though dev tools still run on laptops and in CI, so do not ignore them.
Install scripts. hasInstallScript marks packages that run code at install time. A list of those entries is a short, high value review target, since a script runs before you have used the package at all.
Where it came from. The resolved field shows whether a package came from the registry, a git commit, or a local link. Git and link entries have no registry version to match against advisories, so a scan may not know what they contain.
Links. A link entry points at a local directory, common in workspaces. The target is listed in the lockfile on its own, so look there for the version, and scan that code as your own source.
A short audit recipe
- Count entries with hasInstallScript and read the list.
- List entries whose resolved value is not a registry URL.
- Check which vulnerable packages carry the dev flag, and say so in the report.
- If anyone edits files in node_modules during debugging, delete the hidden lockfile and reinstall before trusting a scan of that directory.
Try it yourself
Open the demo and look at how each component in an inventory carries its source and version. Compare that with the flags above.
Click through it at your own pace, or open it full screen.
Safeguard's SBOM and dependency scanning produces an inventory from your projects, and the free scan shows how a repository appears.
Quick check
Should I commit node_modules/.package-lock.json?
No. It is a local file inside node_modules, used by npm to speed up work. The committed file is package-lock.json at the project root.
What does devOptional mean?
Per the npm docs, the package is both a dev dependency and an optional dependency of a non-dev dependency.
Sources
Last reviewed: 19 October 2026.