Install Scripts and Audits

Blocking Install Scripts and Auditing Beyond npm

The most effective single defense is to stop running dependency code at install time. pnpm 69,400 and Yarn Berry 11,798 refuse dependency build scripts by default (Build Script Approval), and npm 2,036 is following: npm 11.19 warns (Typosquatting), and npm 12 (8 July 2026, not yet bundled with Node.js 2,131 ) blocks them unless package.json allows them. Repeat the dependency-confusion install with npm 12.1.0, installed locally beside the system npm.

The same install under npm 12, then approving the scriptShell
cd ~/v5-ch1/supply/consumer && rm -rf node_modules package-lock.json && npm pkg delete dependencies
N=~/v5-ch1/tools/npm12/node_modules/.bin/npm && $N --version
NPM_TOKEN=npm_fake_demo $N install booknest-internal-utils@^1.0.0 --foreground-scripts \
  --no-audit --no-fund
$N install-scripts approve booknest-internal-utils
jq .allowScripts package.json
Output
12.1.0
added 1 package in 394ms
npm warn install-scripts 1 package had install scripts blocked because they are not covered by
  allowScripts:
npm warn install-scripts   booknest-internal-utils@1.99.0 (postinstall: node steal.js)
npm warn install-scripts
npm warn install-scripts Run `npm install-scripts ls` to review, or `npm install-scripts
  approve <pkg>` to allow.
Approved booknest-internal-utils:
  added booknest-internal-utils@1.99.0
{
  "booknest-internal-utils@1.99.0": true
}

The postinstall never ran. Approval is pinned to the exact version (allow-scripts-pin), so a worm's next patch is blocked again until reviewed; strict-allow-scripts makes the warning fail CI. The wrong package was still installed, though, and runs the moment BookNest calls require(), so the scope rule of Typosquatting still matters. The second cheap defense is a release-age delay, which keeps a fresh version out of your installs until scanners and maintainers have had time to look at it. Yarn 4.18.1 applies one by default and refused the lab's package outright: All versions satisfying "^1.0.0" are quarantined, since both were minutes old.

Install-script and release-age defaults in September 2026
Manager Install scripts Release-age delay
npm 11 Run, with a warning min-release-age (days, off)
npm 12 Blocked; allowScripts min-release-age (days, off)
pnpm 11+ Blocked; allowBuilds minimumReleaseAge, 1 day
Yarn 4 Blocked; enableScripts npmMinimalAgeGate, 1 day
Bun 73,307 Trusted list only minimumReleaseAge in bunfig
pip 21,050 , uv 90,229 , Cargo 6,602 Build code runs uv --exclude-newer

Other ecosystems have their own auditors (pip-audit, cargo audit, OWASP Dependency-Check, brew 6,457 vulns), and multi-ecosystem scanners such as Trivy 67,732 read every lockfile at once.

Scanning npm, uv, Cargo and Gradle lockfiles with TrivyShell
cd ~/v5-ch1/audit-demo     # java, node, python, rust: this chapter's lockfiles, plus jinja2 3.1.2
trivy fs --quiet --scanners vuln --format json . | jq -r '.Results[]
  | "\(.Target) (\(.Type)): \([.Vulnerabilities[]?.VulnerabilityID] | join(" "))"'
Output
java/gradle.lockfile (gradle):
node/package-lock.json (npm):
python/uv.lock (uv): CVE-2024-22195 CVE-2024-34064 CVE-2024-56201 CVE-2024-56326 CVE-2025-27516
rust/Cargo.lock (cargo):

BookNest, the Rust tool and the Java service are clean today; the Python project's deliberately old Jinja 15,439 pin has five known CVEs. Run such a scan on every change (GitHub adds Dependabot 29 alerts, Scanning with Trivy and Grype scans images), and read a clean result as "no known vulnerabilities": a worm published an hour ago is in no database yet, which is what release-age delays and blocked install scripts are for.