Releases: Booyaka101/npm-script-lens
Release list
v1.4.0 — MCP server speaks both spec revisions
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_metaoff every incoming request (2026-07-28 path)- the
initializehandler 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
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
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, orrootcommitted while a transitive git dep exists) - over-permissive —
allcommitted whererootor nothing suffices: the least-privilege ratchet - invalid —
allow-git=trueor 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-checknow also fails when git/remote deps exist and the committed config is insufficient or invalid — the same silent-CI-break shape as a missingallowScriptsblock, in the same fast no-scan gate.doctorreports 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 thatallow-git=rootis unreliable on npm 11 (npm/cli#9189, closed via PR #9206 — root-level git deps were wrongly rejected): preferallthere.- GitHub Action
sources-checkinput (default'false', opt-in): fails the job with an::errorannotation and a job-summary line when the committed.npmrcdoesn'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
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 implicitnode-gyp rebuild(rootbinding.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). --jsonemits{ 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
Patch release.
- Fixes
audit --since <ref>on Windows, where git rejected the base lockfile path as "outside repository" when itsrev-parse --show-toplevel(long name) disagreed withos.tmpdir()'s 8.3 short name (e.g.runneradminvsRUNNER~1). The base lockfile is now resolved viagit 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
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;--writeto the native allowlist,--ci-checkto 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.- Governance —
script-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 --hookpre-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-high128 tests, all four package managers live-verified. Full detail in CHANGELOG.md.
v0.7.0
Everything since v0.5.0 — now on npm as npm-script-lens@0.7.0.
v0.7.0
reviewsubcommand: 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 thatapprove-scripts --allow-scripts-pendingnever surfaces butnpm ci --strict-allow-scriptsrejects (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--sarifsurfaces; the Action gates the check on the runner's npm major version.
v0.5.0 — committed audit manifest
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. --checkprints a human-readable+/-/~drift diff; a bump in theversionfield flags that the detector itself changed and results are worth re-reviewing.- Action gains
manifest-check/manifest-fileinputs; 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
Three of the tool's documented limitations are now closed in code:
- Cross-package bin resolution —
husky 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.7plus 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 inputdeep) — barerequire()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
Patch: Action description shortened to fit the GitHub Marketplace 125-character limit. No functional changes — see v0.3.0 for the feature release notes.