Skip to content

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.