← Back to npm Lifecycle Script Check

This checker vs. diffing package-lock.json by hand

Every fact this tool reports is already in your own package-lock.json or node_modules -- hasInstallScript: true is a real field npm writes, and anyone with an editor and an older copy of the lockfile can grep for it and compare. This page names no product or company: keeping an old lockfile around and diffing it by eye is a manual habit, not a competing tool.

It genuinely works right after a dependency bump on a small project, checked by someone who remembers to do it. It gets harder to trust the moment the lockfile runs to hundreds of packages, the check has to happen on every merge rather than the one time you thought to look, and a flagged package's actual script still needs reading -- which is exactly what the table below covers.

CaseDiffing package-lock.json by handThis tool
A small project, checked right after a dependency bumpWorks fine -- a short lockfile is easy to hold in your head for one checkWorks the same way, instantly, with no need to remember to look
A lockfile with hundreds of packagesOne line flipping to hasInstallScript: true is not visually distinct from the thousands of lines around it in a diff-by-eyeParses every entry and reports exactly which name changed, every time, regardless of lockfile size
Knowing what a flagged script actually doespackage-lock.json only ever records the boolean -- reading the text means separately opening that package's package.json inside node_modulesnode cli.mjs snapshot reads the real script text from local node_modules directly, and classifies it against known attacker patterns
A lockfileVersion 1 fileHas no hasInstallScript field at all -- a grep that finds nothing can misread "field absent" as "nothing installs a script"Detects the missing packages map and says plainly that this format predates the field, instead of reporting a false all-clear
Re-checking on every dependency bump or Dependabot mergeManual, and the first step skipped once nothing looked obviously differentOne CLI command against the last committed snapshot; exits non-zero only when something newly gained a script, so it is safe to wire into CI

The gap that matters most: a boolean is not the script

package-lock.json's packages map records hasInstallScript as a single true/false per entry, because the lockfile stores the resolved dependency tree, not each package's own package.json contents. Spotting that a package now carries the flag tells you a script exists and nothing about what it does -- piping a download into a shell reads identically to a native addon compiling itself, until you open the actual file. A hand check that stops at the boolean has done half the job; this tool's snapshot command reads the script text straight from local node_modules and flags it against the same shell-pipe, network-call, and obfuscation patterns the incidents in this venture's README actually used.

The gap that matters second: the file nobody re-reads

A lockfile diff only catches a newly install-scripted package if someone actually opens the previous commit's copy and compares it, line by line, against the current one -- and a project with a few hundred dependencies renders that comparison unreadable by eye long before it becomes impossible. That is the exact window the four incidents in this venture's README used: none of them needed a victim to be careless, only for the routine "did anything change" check to not happen on that particular npm install. This tool's diff command does the same comparison in one pass and exits non-zero only when something newly gained a script, so it is the check that keeps happening after the novelty of a hand-check wears off.

When to still read the file yourself

This tool's risk tags -- pipes-a-download-to-a-shell, makes-a-network-call, obfuscated-or-encoded -- are a keyword match against patterns real attacks have used, not a verdict. A flagged script still deserves your own read: a legitimate installer can also fetch a prebuilt binary or a font file over HTTPS. What a hand check cannot do at scale is decide, every time, which of several hundred packages is worth that closer read in the first place -- this tool narrows that list for you; reading the flagged handful is still yours to do.

Common questions

Isn't hasInstallScript: true just a grep away?

Finding the field is a grep away. Knowing whether that is new since your last check is not -- grep on one lockfile has no memory of the previous one, so you still have to keep an older copy around and diff the two by eye. That is exactly the step this tool automates, and exactly the step most likely to be skipped after a routine dependency bump that shouldn't have changed anything.

How hard is it to spot one changed line in a real lockfile?

A mid-sized project's package-lock.json commonly runs several thousand lines across several hundred packages. The one line that flips from absent to hasInstallScript: true on a package you have never had reason to look at twice is not visually distinct from the thousands of lines around it -- there is no diff-by-eye reason it would catch your attention over a version bump on the same package.

If I do spot a new hasInstallScript: true, doesn't that tell me everything?

It tells you a script exists, never what it does -- lockfileVersion 2 and 3 record only the boolean, not the script text, because the lockfile stores the resolved tree rather than each package's own manifest contents. Reading the actual script means opening that one package's package.json inside node_modules by hand, which is a second, separate step the lockfile diff does not do for you.

What if my lockfile is lockfileVersion 1?

Then hasInstallScript is not in the file at all -- the field was added in npm 7's lockfileVersion 2. A hand check that greps for the field and finds nothing can misread "the field does not exist in this format" as "no package has an install script," which is the wrong conclusion for exactly the file where it is easiest to reach.

When does a manual look still matter?

Once this tool flags a package, reading its actual script text yourself is still the right next step -- a keyword match for a shell pipe, a network call, or obfuscated code means "read this one," not "this is malware." The tool narrows several hundred packages down to the handful that changed; deciding whether a specific flagged script is a real problem is still a human judgment.

← Back to the checker