Install scripts

npm install runs a dependency's preinstall, install and postinstall hooks as part of installing it. Before your test suite, before your own first line of code, with your environment and whatever your ssh agent is holding.

That is the whole argument for high. For these packages the gap between "a name appeared in the lockfile" and "that name's code ran on this machine" is one command, and no review step fits inside it.

$ ./target/release/stranger scan -v fixtures/npm-m.package-lock.json

  npm-m.package-lock.json  576 packages   (20 direct · 556 transitive · 6 workspace)

  ⚠  INSTALL SCRIPTS        4     arbitrary code at install time
     esbuild@0.27.7                         runs code at install time · lockfile records the flag, not the script
     fsevents@2.3.3                         runs code at install time · lockfile records the flag, not the script
     sharp@0.34.5                           runs code at install time · lockfile records the flag, not the script
     unrs-resolver@1.12.2                   runs code at install time · lockfile records the flag, not the script

The signal

One field:

$ jq '.packages["node_modules/esbuild"] | {version, hasInstallScript}' fixtures/npm-xl.package-lock.json
{
  "version": "0.28.1",
  "hasInstallScript": true
}

That is everything lockfileVersion 3 records. Not the body of the script, not which of the three hooks, not even the script's name. The body is in the tarball on the registry, and stranger does not fetch.

What it cannot see

esbuild unpacking a platform binary and a package curling a payload produce the identical line in this report. Reading that line as triage is the mistake the rule's own source comment exists to prevent, and the detail string is worded so it never implies otherwise: runs code at install time · lockfile records the flag, not the script.

A finding here is a list of packages to look at, ordered by nothing. It is not a verdict on any of them.

What is excluded

The root project's own hasInstallScript is your build, not a stranger's. So are a workspace member's and its link: true symlink. All three are dropped.

jq counts 9 flagged entries in npm-xl; the reader reports 8. The missing one is the root entry, deliberately.

Copies count separately

One finding per lockfile entry, not per name. Two entries for one name are two installs, and two installs run the hook twice, so both are reported. fsevents appears twice in npm-xl — once at the top level and once under a workspace member — and tests/rules.rs asserts both hits are there:

$ jq -r '.packages | to_entries[] | select(.key | endswith("node_modules/fsevents")) | "\(.key)  \(.value.version)"' fixtures/npm-xl.package-lock.json
apps/desktop/node_modules/fsevents  2.3.2
node_modules/fsevents  2.3.3

Both are reported:

$ ./target/release/stranger scan -v fixtures/npm-xl.package-lock.json | head -12

  npm-xl.package-lock.json 1,376 packages   (150 direct · 1,226 transitive · 14 workspace)

  ⚠  INSTALL SCRIPTS        8     arbitrary code at install time
     agent-browser@0.26.0                   runs code at install time · lockfile records the flag, not the script
     electron@40.10.2                       runs code at install time · lockfile records the flag, not the script
     electron-winstaller@5.4.0              runs code at install time · lockfile records the flag, not the script
     esbuild@0.28.1                         runs code at install time · lockfile records the flag, not the script
     fsevents@2.3.2                         runs code at install time · lockfile records the flag, not the script
     fsevents@2.3.3                         runs code at install time · lockfile records the flag, not the script
     node-pty@1.1.0                         runs code at install time · lockfile records the flag, not the script
     unicode-animations@1.0.3               runs code at install time · lockfile records the flag, not the script

Version is part of the sort key, so the order is stable across scans.

It fires on npm only

requirements.txt records nothing equivalent. A pip source distribution can run whatever setup.py wants during installation and the file does not say that it will, which is a real blind spot rather than a rule that does not apply — see Limits.

Reading a hit

The poisoned fixture shows why this rule is worth having next to the hallucination rule rather than instead of it:

$ ./target/release/stranger scan --format json fixtures/poisoned.package-lock.json | jq -r '.findings[] | select(.rule=="install-script") | .package'
lodahs
sharp
unrs-resolver

lodahs appears under two rules that mean different things. One says the name has no evidence behind it. The other says that if you install it, it runs code. Both are true and neither implies the other.

$ ./target/release/stranger scan -v fixtures/npm-m.package-lock.json