A library's scan and an app's scan can differ because the library's lockfile does not travel with it. The npm docs say package-lock.json cannot be published and is ignored anywhere except the root project. An app that installs your library resolves its own tree, so versions can differ from what you tested.
Two files, two jobs
A manifest such as package.json holds ranges. The npm docs show examples: a caret range means "compatible with" a version, and an x range such as 1.2.x covers 1.2.0, 1.2.1 and so on but not 1.3.0. A manifest says what is allowed.
A lockfile records what was chosen. The npm docs describe package-lock.json as describing the exact tree that was generated, so later installs produce identical trees regardless of intermediate dependency updates.
| Manifest | Lockfile | |
|---|---|---|
| Holds | Allowed ranges | Exact resolved versions |
| Published with a library | Yes | No, for package-lock.json |
| Decides your app's tree | Partly | Yes, for the root project |
The shrinkwrap exception
The same docs describe npm-shrinkwrap.json as a file with the same format that does allow publication and defines the tree from the point it is encountered. They say this is not recommended unless you are deploying a CLI tool or otherwise using publication to produce production packages. For most libraries, assume no lockfile ships.
What this does to your scan results
Your library repo has a lockfile, so a scan of it sees exact versions: the ones your CI resolved. An app that depends on the library sees only the library's ranges, and resolves them against whatever is current when the app installs, within its own lockfile.
Three consequences follow:
- A finding in your library's tree may not exist in the app, if the app pins a newer version of that transitive package.
- A clean library scan does not mean every consumer is clean, because consumers can resolve older allowed versions.
- A consumer can have a finding you never saw, because a package in an allowed range was affected on their install date.
What library maintainers can do
- Keep ranges as tight as is reasonable, and raise the lower bound when a fix lands.
- Test against the lowest and highest versions your ranges allow, not only what the lockfile pins.
- Publish an SBOM or a dependency list with each release so consumers can see what you tested with.
- Say in release notes when a minimum version is raised for security reasons.
What app teams should do
Scan your own lockfile, not the library's repo. The lockfile in your root project is the one that decides what you run. Use the scan of a dependency's repo as a hint only.
For why ranges and locks both matter, see committing a lockfile for apps and libraries.
Try it yourself
Click through it at your own pace, or open it full screen.
Look at how direct and transitive packages are listed with their resolved versions, which is the view your own lockfile gives you.
Quick check
Should a library commit its lockfile?
It is useful for your own CI and scans, even though it will not be published to consumers.
Does a clean scan of a library mean apps are safe?
No. Apps resolve their own versions inside the allowed ranges, so they can differ.
Safeguard offers dependency scanning with SBOM output and reachability analysis, and the free scan lets you try it on your own project.
Sources
Last reviewed: 19 October 2026.