A build time dependency scan should read the lock file, because it records the exact versions that will be installed, including transitive ones. A manifest holds ranges, so a scan of it alone can describe a different tree from the one you ship. Use the manifest only as a fallback.
Definitions
A manifest (package.json, requirements.txt, pom.xml and similar) lists what you asked for, often as version ranges. A lock file records what the resolver actually chose, version by version, for direct and transitive packages. A scan is only as accurate as the list it reads.
What the npm docs confirm
npm's clean install command is built for automated environments. It requires an existing package-lock.json or npm-shrinkwrap.json, errors if the lock file and package.json disagree, never writes to either file, and removes any existing node_modules first. Those are exactly the properties a build time scan wants: a frozen, repeatable input.
The audit command goes further. By default it requires a package lock or shrinkwrap to run, and the documentation warns that bypassing the lock means results may differ on every run, because npm rebuilds the dependency tree each time.
pip works differently. Its documentation says hash checking mode needs all requirements pinned and hashed, and that every dependency must be spelled out in the requirements file. A fully pinned and hashed file plays the lock file's role there.
Comparison
| Input | Strength | Weakness |
|---|---|---|
| Manifest only | Always present | Ranges, so the scanned tree may not match the shipped one |
| Lock file | Exact versions, transitive included | Needs to be committed and kept current |
| Resolved install in CI | Reflects reality | Scan happens after the install |
| Built artifact or image | Shows what actually shipped | Later in the process, harder to attribute to a change |
Steps to get this right
- Commit lock files for applications. For libraries, follow your ecosystem's guidance, but still resolve a full tree in CI.
- Install in CI with the frozen command so a mismatch fails the build instead of silently updating.
- Point the scanner at the lock file, and check that it lists transitive packages.
- Store the scan output, or an SBOM generated from the same tree, with the build.
- For container builds, scan the final image too, since system packages are not in an application lock file.
Common mistakes
- Scanning before install and after a later update. If a step modifies the tree after the scan, the result is stale.
- Ignoring dev dependencies. They run in CI and on laptops. Decide on purpose whether to include them.
- Treating "no lock file" as fine. It means your scan results are not reproducible.
Try it yourself
Click through it at your own pace, or open it full screen. Compare the direct dependencies with the transitive ones that make up most of the inventory.
Where Safeguard fits
Safeguard performs dependency scanning and SBOM generation from your project files and builds, and you can try it free at gold.safeguard.sh/scan. For a related comparison, see runtime SBOM vs build time SBOM.
Quick check
Is a manifest scan useless without a lock file?
Not useless, but approximate. It reflects ranges, so treat results as indicative and add a lock file to make them exact.
Do I need to scan both the lock file and the image?
For containers, yes. The lock file covers application packages. The image adds operating system packages and anything copied in during the build.
Sources
- npm Docs, npm ci
- npm Docs, npm audit
- pip documentation, Secure installs
Last reviewed: 19 October 2026.