If a project has no lockfile, the dependency tree is resolved fresh each time from version ranges, so two scans can see different versions. Commit a lockfile, scan that, and both tools will read the same exact versions. Then differences come from data, not from the tree.
Ranges move, lockfiles do not
In package.json, dependencies are written as ranges. The npm documentation lists the forms: exact versions, tilde ranges that allow patch changes, caret ranges that allow changes that do not alter the leftmost non zero digit, wildcards, comparators and combinations. A range like caret 1.2.3 accepts any later compatible release.
That means the version installed on Monday can differ from the one installed on Friday if a new release appeared in between. A scanner that resolves ranges at scan time will report whatever it resolved. A second scanner running an hour later, or on a different machine with a different cache, can resolve something else.
What npm itself says
The npm audit documentation says that by default npm requires a package-lock or shrinkwrap in order to run the audit. You can bypass that, but then results may be different with every run, since npm will rebuild the dependency tree each time. That sentence describes exactly the drift this post is about.
Steps to remove the drift
- Commit a lockfile. Generate it with a normal install and commit it with the manifest.
- Point every scanner at the lockfile. A tool that reads only the manifest will resolve ranges again, so check its settings.
- Install from the lockfile in CI. Use the install command that follows the lockfile exactly, not one that updates it, so the build matches the scan.
- Review lockfile changes like code. A lockfile diff is where new versions enter your tree. Require review on it.
- Rescan after updates. When Dependabot or a human changes the lockfile, scan the new state.
| Setup | What two scanners see |
|---|---|
| No lockfile, ranges only | Possibly different versions per run |
| Lockfile committed, both tools read it | The same exact versions |
| Lockfile present, one tool reads manifest | Different trees, avoidable |
| Lockfile and built artifact scanned | Strongest match to what ships |
A caution about what a lockfile does not fix
A lockfile pins what your tree contains today. It does not stop new advisories from arriving tomorrow, so a clean scan is a snapshot of one day. Keep scanning on a schedule, not only on commits.
It also does not prove the artifact in production matches the lockfile. If your pipeline builds a container or a bundle, scan that output as well.
Libraries are a special case
If you publish a library, your consumers resolve ranges on their own machines, and your lockfile is not shipped to them. Their trees can differ from yours. That is a separate question from comparing two tools on your own repository.
Quick check
Is a lockfile enough to make two scanners agree?
It removes version drift. Differences can remain from advisory data, scope settings and counting units.
Should the lockfile be committed for an application?
Yes, for applications it is the normal practice, because it makes installs and scans repeatable.
Sources
Last reviewed: 19 October 2026.