Skip to content

v0.33.0

Choose a tag to compare

@github-actions github-actions released this 08 Sep 11:55
· 41 commits to main since this release

The part of a pull request nobody reads line by line.

Your dependencies' install surface, compared

The delta axis has been able to tell you since v0.27.0 that something
which auto-executes in your repository changed. It could not see a
dependency that starts executing on npm install — because that is not
a new file and not a code change, it is a lockfile diff. And a lockfile
diff is the one part of a pull request nobody reads line by line, that
branch protection and required reviews do nothing about, because there
is nothing in it a reviewer would recognise as suspicious.

The scan now reads package-lock.json / npm-shrinkwrap.json on the
default branch and records the subset of dependencies that carry an
install script, keyed by name@version. Three cases, and the key is
what separates them:

  • a name that carried no install script and now does — a dependency
    began executing code at install;
  • the same version whose integrity hash changed — that version was
    republished, which no upgrade explains, and the heaviest thing this
    axis says;
  • a known install-script package at a new version — an ordinary
    bump. Listed, never scored. So is one that stops running code.

No new permission is needed: the lockfile is a blob in your own tree.

Why the subset, and not the lockfile

Fingerprinting the file would fire on every dependency bump, and an axis
that fires on everything is one you learn to skip.

Measured over the last twenty lockfile revisions of axios/axios,
npm/cli and nodejs/undici — fifty-seven in all — the file churned
constantly and the install-script subset moved twice. The same
version was republished zero times. npm/cli is the strongest case:
a large lockfile in constant motion whose install-script set did not
move once in nineteen revisions.

That zero is the reason the republish case can carry real weight. It is
a measured base rate of false positives, not evidence the signal fires,
and it is what lets the sharp case mean something instead of training
you to ignore it.

What it does not compare, it says

Every one of these is a weight-0 line, and each exists because the
alternative is silence — which is indistinguishable from nothing here
runs code at install
. A reader told nothing assumes coverage.

  • npm only, and the report says so. pnpm carried a build declaration
    at lockfileVersion 6 and dropped it at 9; Yarn never had one. Building
    on a field that has already disappeared once would ship a rule that
    decays. A repository whose only lockfile is theirs gets a line naming
    it and saying its dependency surface was not compared.
  • No lockfile at all — common rather than anomalous, and still worth a
    line, because "no findings" and "nothing to look at" are different
    answers. It needs a package.json to fire: no npm lockfile on a Go
    repository is not a disclosure.
  • A lockfile too large to read, a schema this scan has not measured,
    content that would not decode, a surface recorded before and not
    measured now.
  • More changes than the report will list, with the number stated — and
    the ones it keeps are the most serious, not the first alphabetically.

octoscope never looks at the registry. The claim is exactly your
dependencies' auto-execute surface changed
, never this dependency is
malicious
.

Notable polish

The scan's recorded fingerprint gains the dependency surface beside the
ignition hashes, and the two are kept apart deliberately: a recorded
empty set means the file was read and nothing in it runs code at
install, while no entry means the comparison did not happen. A store
written before this release loads as the second, so the first scan after
upgrading says first comparison rather than nothing changed.

Lockfiles are exempt from the blob-anomaly axis. A real one runs 343 KB
to 437 KB and every entry carries a base64 hash — precisely the shape
that axis scores — so without the exemption every ordinary JavaScript
repository would have been flagged for being ordinary.

Under the hood, for the curious

Two adversarial review passes found nine defects between them, and the
five that mattered most were in code no reviewer had seen yet — the
range between the first pass and the tip. The sharpest: the cap on how
many findings one lockfile may contribute cut a list in key order, so
twenty-five harmless bumps named early in the alphabet could push a
republish named late out of the report and out of the score. A
lockfile could therefore choose which finding survived. It now keeps the
most serious.

Also from that pass: a resolved URL is not an integrity hash, so a
registry or mirror change is reported as a move rather than scored as a
republish; npm-shrinkwrap.json is usually a byte-identical copy of
package-lock.json, so the two share one git blob and were briefly
counted twice; and a package name carrying a newline could forge a
visual line in the report.

Tests

Hermetic tests cover the parser, the read and its budget, the
fingerprint's round trip through disk, all three delta cases, every
disclosure, and byte-identical determinism over thirty runs of a fixture
that exercises the lot. The lockfile read is proven not to spend the
blob-analysis budget by scanning the same tree with and without a
lockfile and comparing what the other axis actually fetched.

Every assertion was proven to bite by breaking what it guards and
restoring it — thirty such checks across the cycle, each failing
its own test and only its own. Three honest gaps are documented and
filed rather than described and forgotten: #157, #158, #159.

Upgrade

brew upgrade gfazioli/tap/octoscope

Or gh extension upgrade octoscope, go install github.com/gfazioli/octoscope@latest,
or pull ghcr.io/gfazioli/octoscope:latest.