A package.json file is easy to skim. You see the packages your team added, maybe a few scripts, and a version number beside each dependency. It feels like a reasonable picture of what your application installs.
It is only the starting point.
The versions npm actually installs are recorded in the lockfile. Those packages can bring in more packages of their own. Some can run code during installation. And a clean vulnerability report cannot tell you whether a package published this morning is safe.
Here are five things worth checking when you review a package.json file.
1. A Version Range Can Accept a Release You Have Not Reviewed
Say your project depends on this:
{
"dependencies": {
"example-library": "^2.4.0"
}
}That does not ask npm for one exact version. It permits compatible releases within the range. If your lockfile already records a version that satisfies the range, npm can use it. If the lockfile is missing or needs updating, a new install may resolve a newer release.
There is nothing inherently wrong with version ranges. The problem is treating a change in the resolved version as if nothing changed because the line in package.json still looks familiar.
Review changes to package-lock.json alongside changes to the manifest. In CI, use npm ci: it installs from the lockfile and fails when the lockfile does not agree with package.json, rather than updating it during the build.
2. An Install Can Run Someone Else’s Code
When you run npm install, you are not necessarily just downloading files. Packages can define lifecycle scripts such as preinstall, install, and postinstall. npm may run them as part of the installation.
There are good reasons for install scripts. A package might need to compile a native component, for example. Still, it is worth asking what an install job can access. Does your CI runner have deployment credentials? Can a developer’s machine read tokens or local configuration that the package does not need?
Review install-time behavior when adding or upgrading dependencies. Keep credentials out of install jobs where you can. If your project allows it, test npm ci --ignore-scripts, but check the resulting build before making that your default. Some legitimate dependencies need their scripts to work.
Blocking a risky package before download helps. Once you allow a package, you still need to think about what its scripts can do.
3. The Package With the Vulnerability May Be Missing From Your File
You run npm audit and see a package name you do not recognize. You search package.json, but it is not there.
Often, it is a transitive dependency. One of your direct dependencies needs it, or another package further down the tree does. Your application can therefore install and use packages your team never added by name.
Start by tracing why the package is present with npm explain <package-name>. Check the installed version in the lockfile, not just the top-level dependency that brought it in. If you use an overrides entry to address a vulnerable transitive version, revisit it as the surrounding dependencies change.
This is why a short package.json is not the same thing as a small dependency footprint.
4. A Dependency May Not Come From the Registry You Expect
A dependency entry can point to more than a normal npm package version. npm supports Git references, tarball URLs, local paths, and package aliases.
That flexibility can be useful. It can also make a dependency harder to assess at a glance. A Git reference may resolve differently from a registry release. An alias may make the name used in your code look different from the package npm fetches. A direct URL gives you another source to trust.
Look through your dependencies and overrides for git+, github:, http:, https:, file:, and npm:. Then check the lockfile to see what actually resolved. Where practical, pin Git dependencies to an intended commit.
If your organization routes registry requests through a security proxy, remember that a Git clone or direct download may take a different path. It needs its own controls.
5. A Clean Audit Result Can Leave an Important Question Open
npm audit is useful for finding known vulnerabilities. It cannot report a vulnerability that has not been discovered or disclosed. A version published very recently may have no advisory because nobody has had much time to examine it.
That leaves a practical decision. When a developer requests a new package version, do you allow it if nothing bad is known yet? Or do you wait until you have sufficiently current information to assess it?
Neither answer fits every team. A development workspace might favor continuity while it builds visibility into package use. A more sensitive workspace might require verification before an unknown version is allowed. A release grace period gives teams another option: wait a chosen number of days before accepting a newly published version.
ShieldedStack lets teams make that decision per workspace. Its Trust Then Verify and Verify Then Trust modes use the same package policy, but handle missing or stale assessment information differently.
Start With the File, Then Follow the Install
package.json tells you what your project asks for. To understand what enters your environment, you also need to look at the lockfile, the full dependency tree, install scripts, and the source of each package.
A good review starts with a few questions:
- Did the resolved versions change?
- What transitive packages came with them?
- Will anything run during installation?
- Where will npm fetch each dependency?
- What do we do when a requested version has no current assessment?
A lockfile, npm audit, and careful CI configuration each answer part of that picture. ShieldedStack adds policy at the point where registry packages are requested, so teams can decide whether a version should be delivered before it reaches a developer machine or build runner. It is another layer, not a replacement for reviewing what your project installs.