Trivial packages

Dependencies whose whole job is one expression. left-pad was one of these, and so was event-stream.

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

  npm-xs.package-lock.json 37 packages   (1 direct · 36 transitive)

  ⚠  TRIVIAL                4     (10.8% of third-party)
     es-errors@1.3.0          one expression, one publisher · inlining it removes an account from your build
     gopd@1.2.0               one expression, one publisher · inlining it removes an account from your build
     has-symbols@1.1.0        predicate-shaped, resolves nothing · size not measured, see rule docs
     hasown@2.0.4             one expression, one publisher · inlining it removes an account from your build

  ·  UNPINNED               — no signal in this format

  risk 9/100    10ms    third-party deps used to compute this: 0

It does not measure triviality

Start here, because everything else on this page depends on it.

A package-lock.json entry holds version, resolved, integrity, license, engines and the dependency lists. There is no unpacked size, no file count, no export list, no line count anywhere in the format. All of that is in the tarball, the tarball is on the registry, and stranger does not fetch.

So this rule recognises names. Nothing it prints should be read as though it had measured anything.

Two clauses, and they are different in kind

A hand-written list. Two dozen packages whose published purpose is a single expression or a re-export of a builtin — isarray, is-number, left-pad, object-assign. Picked by hand, which makes the boundary somebody's judgement rather than a threshold. Against a registry holding millions of names, two dozen is nothing. That is the honest size of the clause, and there is no version of it that is not a list somebody wrote.

Shape. A name that reads as a predicate (is-…, has-…, scope stripped) and that resolves no dependencies of its own. Both halves come out of the lockfile. The second half is what stops it firing on is-glob and has-tostringtag, which turned out to need help.

The detail string keeps the two apart, because they deserve different amounts of trust:

one expression, one publisher · inlining it removes an account from your build
predicate-shaped, resolves nothing · size not measured, see rule docs

How clause 2 is wrong

It has no idea how long a file is.

is-callable is dozens of lines of edge cases around one typeof. is-docker reads /proc and memoises the answer. Both are predicate-shaped, both resolve nothing, both are reported, and neither is a one-liner. That is the false-positive mode, and it is not the exception — it is a good share of what clause 2 finds on a real tree.

A hit is worth twenty seconds of attention. It is not a verdict.

It under-reports at least as badly

function-bind, wrappy and util-deprecate are in the same weight class as anything on the list and are not on it, because nobody is going to claim to have read them all. Clause 2 is blind to any micro-package that depends on another micro-package — once needs wrappy — and to every one that is not named like a predicate, which is most of them.

Why low

None of this is a vulnerability. It is a count of publishers who can push straight into your build, for code you could have inlined. left-pad and event-stream were both packages this size, so the count is worth having. It is never urgent, and it collapses to a count unless you pass -v.

The percentage

  ⚠  TRIVIAL                29    (2.1% of third-party)

The denominator is the header's own package count — third-party entries only. The rule never looks at a first-party package, so a workspace member cannot be a hit, and counting one in the denominator would print a share of a population the numerator was never drawn from.

That was the bug for most of the weekend: the percentage divided by every entry in the lockfile while the header divided by third-party ones. On npm-m it read 17/582 where the header's own numbers say 17/576 — 2.9% against 3.0%. Under a tenth of a point on every fixture here, which is exactly why it survived so long. tests/cli.rs now derives both numbers from one line of output and fails if they ever disagree again.

Duplicates collapse

npm nests the same version of is-extendable under two different parents in npm-xl. Two install hooks would be two events, so install scripts reports both. Two copies of one expression are one fact, so this rule prints it once. Different versions still count separately — is-docker at 2.2.1 and 3.0.0 are two findings, and version drift is the rule that cares that both exist.

$ ./target/release/stranger scan --format json fixtures/npm-xl.package-lock.json | jq -r '.findings[] | select(.rule=="trivial") | .detail' | sort | uniq -c