Skip to content

Releases: Booyaka101/npm-script-lens

v1.4.0 — MCP server speaks both spec revisions

Choose a tag to compare

@Booyaka101 Booyaka101 released this 27 Jul 10:58

Our own mcp-vet flagged the bundled MCP server (npx npm-script-lens mcp) as BREAKING against the 2026-07-28 spec: that revision removes the initialize / notifications/initialized handshake and carries protocolVersion, clientInfo and capabilities in params._meta on every request instead.

Deleting the handler would have satisfied the linter and broken every client in the field. So this does what mcp-vet's own guidance prescribes — "your rollout is a window, not a day" — and supports both revisions:

  • absorbMeta() reads _meta off every incoming request (2026-07-28 path)
  • the initialize handler stays for 2025-11-25 clients, and now echoes the _meta-negotiated version when the request omits one
  • the residual static finding is suppressed with a justification rather than left as a permanent red CI step

Verified on the wire in both directions: a 2025-11-25 client gets its handshake reply; a 2026-07-28 client calls tools/list with no handshake at all. 201/201 tests pass.

v1.3.0 — the gyp lens

Choose a tag to compare

@Booyaka101 Booyaka101 released this 27 Jul 08:30

Read what is actually inside binding.gyp — and diff it between versions.

Every install-script allowlist/approval tool, this one included until now, only checked that binding.gyp exists and called the result "implicit node-gyp rebuild". But gyp evaluates that file at configure time and runs the shell commands in it (subprocess.run(contents, shell=use_shell) in gyp-next's pylib/gyp/input.py), which made the build file a place to hide install-time code where approval tooling was not looking. That is what the June 2026 npm campaign used — ReversingLabs, 2026-06-04: 286 malicious versions across 56 packages.

The gyp lens

New src/gyp.js reads binding.gyp and the .gypi/.gyp files it includes, with a tolerant reader for the GYP dialect (single-quoted strings, # comments, trailing commas — JSON.parse cannot read real ones). Every channel from gyp-next's early/late/latelate variable regexes is covered:

Channel What it does
<!( <!@( command expansion — gyp runs it in a shell
>!( >!@( ^!( ^!@( the same in gyp's late/latelate phases — one character apart, invisible to a naive scan
<!pymod_do_main( (+ >/^) imports a Python module and calls its DoMain()
<|( >|( ^|( listfile expansion
actions[].action · rules[].action · postbuilds[].action build steps that run commands
make_global_settings replaces CC/CXX/LINK — a compiler hijack
conditions flagged on the Python-eval sandbox escape

Plain <(var) interpolation is never flagged. A structural-parse failure falls back to a raw-text scan marked partial — never a silent pass.

Wired through audit (gyp: signals, scored HIGH), review (findings printed above the raw file), --sarif (new gyp-exec-channel rule), and policy denyCapabilities.

Fix: the diff false negative

diff compared binding.gyp by existence, so a version that rewrote an existing one printed UNCHANGED: implicit node-gyp rebuild and exited 0 — the exact shape the June 2026 wave-2 releases had. It now compares contents: MODIFIED with a line-level diff and gainedChannels, exit 1. --json gains { gyp: { changed, gainedChannels } }.

v12-optional-gap is now version-gated

npm/cli#9562 was fixed by PR #9597 (npm 11.18.0, and in 12.0.0), which skips inert nodes during the script-collection walk. On a fixed npm the detector drops optional deps whose os/cpu exclude your platform, so it no longer tells modern-npm users to allowlist fsevents on Linux. Older npm behavior is unchanged.

⚠️ Re-baseline your manifest

A manifest --check baseline containing native packages may now show a new gyp capability. That is a real capability the tool previously could not see, not drift — re-baseline once with npm-script-lens manifest --write.


174 → 201 tests. CI green on ubuntu + windows × node 20/22; both compatibility canaries (npm-compat, pm-compat) repaired and green.

v1.2.0 — allow-git / allow-remote coverage

Choose a tag to compare

@Booyaka101 Booyaka101 released this 27 Jul 05:03

npm v12 flips three defaults, not one. npm-script-lens has covered allowScripts since day one — this release covers the other two: allow-git and allow-remote, under which git and remote-tarball dependencies stop resolving entirely unless you opt in. Both are the strict enum all | none | root, defaulting to none, and there is no migration tooling upstream — the official discussion's best offer is grep -r 'git+' package.json.

New: sources

npx npm-script-lens sources                # report + the minimal correct .npmrc
npx npm-script-lens sources --check        # CI: exit 1 on insufficient / over-permissive / invalid
npx npm-script-lens sources --write        # merge the minimal values into .npmrc (comment-preserving)
npx npm-script-lens sources --json         # { git, remote, npmrc }
git dependencies (2)
  ROOT        left-pad @ github:left-pad/left-pad
  TRANSITIVE  some-pkg @ git+ssh://git@github.com/a/b.git   via my-lib -> some-pkg
remote dependencies (0)

minimal correct .npmrc:
  allow-git=all

allow-git=all is required because 1 git dependency is transitive; allow-git=root would otherwise suffice.
Re-point or drop `some-pkg` (via my-lib) to tighten this to allow-git=root.

It reads all four lockfile dialects (package-lock v1/v2/v3, yarn classic + berry, pnpm, bun.lock) with zero network calls, classifies each dependency as ROOT (declared in the root package.json, so allow-git=root suffices) or TRANSITIVE (forces all, with the via-chain that drags it in), and tells you exactly which dependency to re-point to tighten back to least privilege.

--check fails in three distinct ways

  • insufficient — npm v12 will refuse the install (no .npmrc, or root committed while a transitive git dep exists)
  • over-permissiveall committed where root or nothing suffices: the least-privilege ratchet
  • invalidallow-git=true or a bare --allow-git, which several published migration guides recommend, is not in the enum; npm treats it as unset and your install still breaks. The check names the valid three.

Also in this release

  • allow --ci-check now also fails when git/remote deps exist and the committed config is insufficient or invalid — the same silent-CI-break shape as a missing allowScripts block, in the same fast no-scan gate.
  • doctor reports git/remote dependency counts, minimal values vs the committed .npmrc, whether your npm has the keys at all (they appeared in 11.10.0 / 11.15.0, checked at full-version precision), and warns that allow-git=root is unreliable on npm 11 (npm/cli#9189, closed via PR #9206 — root-level git deps were wrongly rejected): prefer all there.
  • GitHub Action sources-check input (default 'false', opt-in): fails the job with an ::error annotation and a job-summary line when the committed .npmrc doesn't match the lockfile's git/remote reality.

Compatibility

The audit / analyzer / risk engine is untouched — non-registry dependencies were and are skipped there, so existing reports are byte-identical. The .npmrc emitter is npm-only; for yarn/pnpm/bun lockfiles the dependency report still works and the write is skipped with a note. 174 tests, green on ubuntu + windows × node 20/22.

The VS Code extension is unchanged in this release — the .vsix on the v1.0.1 release still applies.

Full changelog: CHANGELOG.md · Diff: v1.1.0...v1.2.0

v1.1.0 — diff subcommand

Choose a tag to compare

@Booyaka101 Booyaka101 released this 25 Jul 08:40

diff — compare install scripts across two versions

npx npm-script-lens diff <pkg>@<old> <pkg>@<new> shows exactly which install-time behavior an upgrade adds or changes before you bump a pin.

  • Diffs preinstall/install/postinstall + the implicit node-gyp rebuild (root binding.gyp), straight from the registry.
  • UNCHANGED (green) / ADDED (red, incl. implicit node-gyp rebuild (binding.gyp)) / REMOVED (yellow) / MODIFIED (red, with a line-level diff).
  • --json emits { unchanged, added, removed, modified }.
  • Exit 1 on any added/modified script (a CI gate for upgrades that grow their install surface), else 0 (pure removals stay 0).
$ npx npm-script-lens diff sharp@0.32.6 sharp@0.33.0
sharp@0.32.6 → sharp@0.33.0
REMOVED: implicit node-gyp rebuild (binding.gyp)
MODIFIED: install
    - (node install/libvips && node install/dll-copy && prebuild-install) || …
    + node install/check

Full changelog: see CHANGELOG.md.

v1.0.1 — Windows --since fix

Choose a tag to compare

@Booyaka101 Booyaka101 released this 25 Jul 03:40

Patch release.

  • Fixes audit --since <ref> on Windows, where git rejected the base lockfile path as "outside repository" when its rev-parse --show-toplevel (long name) disagreed with os.tmpdir()'s 8.3 short name (e.g. runneradmin vs RUNNER~1). The base lockfile is now resolved via git show <ref>:./<file> from the lockfile's directory — no host-side path math. Adds a monorepo/subdirectory regression test.

No other changes; see v1.0.0 for the feature release. 129 tests, CI green on ubuntu/windows × node 20/22.

v1.0.0 — the install-script governance tool for npm, pnpm, yarn & bun

Choose a tag to compare

@Booyaka101 Booyaka101 released this 25 Jul 03:33

npm-script-lens started as "show me what this install script does before I approve it." v1.0.0 is the whole workflow around that question — across every major package manager, from a developer's editor to CI to AI agents.

One tool, four ecosystems

Behavioral analysis already ran on npm, pnpm, yarn, and bun lockfiles. Now the write side does too: allow/review/sync reconcile each manager's native opt-in — npm allowScripts, pnpm allowBuilds, yarn dependenciesMeta.built, bun trustedDependencies. All four verified end-to-end against the real binaries.

What's new since 0.5.0

  • allow — one-shot classifier that auto-approves SAFE/LOW scripts and holds MEDIUM/HIGH/malicious for review; --write to the native allowlist, --ci-check to fail builds that npm v12 would silently break.
  • review — shows npm v12's pending set plus the actual file contents the scripts run, with the behavioral verdict, OSV, and trust.
  • doctor — self-diagnoses drift from npm's internals and exits non-zero when it stops understanding your npm.
  • Governancescript-lens.policy.json: max risk, denied capabilities, minimum publish age, required provenance, per-package waivers.
  • CI & local — GitHub Action ci-check + sync-check, an auto-fix bot for Renovate/Dependabot branches, init --hook pre-commit, and two drift-canary workflows.
  • Editors — a VS Code extension (risk surfaced inline on package.json) and a Neovim plugin.
  • Reporting — Markdown, JSON, SARIF, and a self-contained HTML dashboard (audit --html).
  • AI — MCP server with audit_package, audit_lockfile, classify_allowscripts.

Install

npx npm-script-lens audit --fail-on-high

128 tests, all four package managers live-verified. Full detail in CHANGELOG.md.

v0.7.0

Choose a tag to compare

@Booyaka101 Booyaka101 released this 24 Jul 13:18

Everything since v0.5.0 — now on npm as npm-script-lens@0.7.0.

v0.7.0

  • review subcommand: lists packages with pending npm v12 approve-scripts approvals alongside the actual install-script content, so you can see exactly what each lifecycle script would run before allowing it.

v0.6.0 — npm v12 approve-scripts gap check

  • v12-optional-gap (npm/cli#9562): optional deps with install scripts that approve-scripts --allow-scripts-pending never surfaces but npm ci --strict-allow-scripts rejects (the fsevents-on-Linux trap).
  • v12-eglobal-risk (npm/cli#9463): npm install -g <pkg> in workflows where the package has install scripts and no --allow-scripts (no post-install approval path exists globally).
  • Both integrate with the Markdown report, --json, and --sarif surfaces; the Action gates the check on the runner's npm major version.

v0.5.0 — committed audit manifest

Choose a tag to compare

@Booyaka101 Booyaka101 released this 23 Jul 12:38

A committable behavior receipt whose git diff is the approval-surface change — the feature @raju_dandigam requested on the launch article (#1).

npx npm-script-lens manifest --write   # writes script-lens.json next to the lockfile
npx npm-script-lens manifest --check   # CI: exit 1 if install-time behavior drifted
  • Stable, sorted, behavior-only map: name@version → { risk, capabilities }. Commit it next to your lockfile and reviewers see capability changes in the normal PR diff — no tooling required.
  • Excludes all trust data (downloads, age, OSV), so the file changes when a package's behavior changes, not when its popularity does. Live malware/trust checks stay in audit.
  • --check prints a human-readable +/-/~ drift diff; a bump in the version field flags that the detector itself changed and results are worth re-reviewing.
  • Action gains manifest-check / manifest-file inputs; drift is written to the PR job summary. This repo's own self-audit workflow now enforces its (empty — we ship no install scripts) manifest.

55 tests green. Full notes in CHANGELOG.md.

v0.4.0 — bin resolution, payload decoding, --deep

Choose a tag to compare

@Booyaka101 Booyaka101 released this 21 Jul 13:39

Three of the tool's documented limitations are now closed in code:

  • Cross-package bin resolutionhusky install-style scripts are no longer conservatively HIGH. When a lockfile package with the same name owns the bin, its actual bin script is fetched and analyzed, and the row is re-scored on real evidence: bin: husky install → husky@9.1.7 plus what the script actually does.
  • Payload decoding — base64 / char-code / eval'd literal payloads are decoded (strict-parse gated, depth-capped) and re-analyzed. Obfuscation findings now show what the hidden code does, not just that it hides. Runtime-assembled payloads remain flagged-but-opaque.
  • --deep (CLI flag + Action input deep) — bare require()s from install-script code resolve into the matching lockfile package's entry file, one level deep, closing the curated-helper-list gap for in-tree helpers.

The remaining limitation is the fundamental one, kept on purpose: static capability detection, not proof of malice.

54 tests green. See CHANGELOG.md.

v0.3.1

Choose a tag to compare

@Booyaka101 Booyaka101 released this 21 Jul 13:26

Patch: Action description shortened to fit the GitHub Marketplace 125-character limit. No functional changes — see v0.3.0 for the feature release notes.