[BUG] strict-allow-scripts false-positives on extraneous (edgeless) registry nodes
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 10.1k
- Forks
- 4.7k
- Avg merge
- 2d 2h
- Merged PRs (30d)
- 19
Description
Is there an existing issue for this?
- I have searched the existing issues
This issue exists in the latest npm version
- I am using the latest npm
This is not just a request to bump a dependency for a CVE
- This is not solely a request to bump a dependency for a CVE
Current Behavior
With strict-allow-scripts=true and a name-only deny entry such as "allowScripts": { "esbuild": false }, npm install aborts with ESTRICTALLOWSCRIPTS, claiming a package has an install script "not covered by allowScripts" — even though that package is explicitly denied by name and is extraneous (no incoming edges, slated for pruning, its install script will never run). The extraneous node exists only as a nested package-lock.json entry, not on disk.
npm error code ESTRICTALLOWSCRIPTS
npm error --strict-allow-scripts: 1 package(s) have install scripts not covered by allowScripts:
npm error esbuild@0.25.12 (install: (install scripts present))
This reproduces under the default (hoisted) install strategy. The precondition is a registry node nested under a workspace's node_modules that nothing depends on: buildIdealTree prunes such an orphan when it is top-level, but retains it when it is nested inside a workspace, so the strict preflight sees it.
The strict gate and the advisory listing disagree on the identical node: npm install-scripts ls reports the tree as fully covered ("No packages with unreviewed install scripts" — it walks the loaded actual tree), while the strict preflight (which walks buildIdealTree) surfaces the orphan as unreviewed.
The bug was first observed with two esbuild nodes in a workspace monorepo, where one copy is depended on and another is an orphan leftover:
| node | edgesIn | isRegistryDependency | extraneous | isScriptAllowed verdict |
|---|---|---|---|---|
| esbuild@0.27.2 (depended on) | 10 | true |
false |
false (deny matches — OK) |
| esbuild@0.25.12 (orphan, nested under a workspace) | 0 | false |
true |
null (unreviewed → error) |
The orphan's lockfile entry has a valid registry tarball URL (https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz) and "extraneous": true.
Expected Behavior
A package that is explicitly denied by a name-only allowScripts entry, or that is extraneous and will be pruned before any script runs, must not gate the install under strict-allow-scripts. The strict gate and npm install-scripts ls should agree on the same node.
Steps To Reproduce
Self-contained CLI reproduction. core-js stands in for esbuild (zero dependencies, declares a postinstall). The nested orphan lockfile entry is injected to model the stale entry that real workspace installs can leave behind; the bug is that npm install then false-positives on it.
mkdir -p /tmp/sas-repro/packages/app && cd /tmp/sas-repro
# 1. a workspace project on the default (hoisted) install strategy
cat > package.json <<'JSON'
{ "name": "root", "version": "1.0.0", "private": true, "workspaces": ["packages/app"] }
JSON
cat > packages/app/package.json <<'JSON'
{ "name": "app", "version": "1.0.0", "dependencies": { "left-pad": "1.3.0" } }
JSON
npm install --ignore-scripts --no-audit --no-fund
# 2. inject an orphan registry node nested under the workspace (nothing depends on it)
npm install core-js@3.38.0 --ignore-scripts --no-audit --no-fund --prefix ./.tmp
node -e '
const fs = require("fs");
const cj = JSON.parse(fs.readFileSync(".tmp/package-lock.json")).packages["node_modules/core-js"];
const l = JSON.parse(fs.readFileSync("package-lock.json"));
l.packages["packages/app/node_modules/left-pad/node_modules/core-js"] = cj;
fs.writeFileSync("package-lock.json", JSON.stringify(l, null, 2));
'
rm -rf .tmp node_modules
# 3. deny core-js install scripts + enable the strict gate
cat > package.json <<'JSON'
{ "name": "root", "version": "1.0.0", "private": true, "workspaces": ["packages/app"], "allowScripts": { "core-js": false } }
JSON
echo 'strict-allow-scripts=true' > .npmrc
# 4. install fails, even though core-js is denied and is an extraneous orphan
npm install --no-audit --no-fund
Observed:
npm error code ESTRICTALLOWSCRIPTS
npm error --strict-allow-scripts: 1 package(s) have install scripts not covered by allowScripts:
npm error core-js@3.38.0 (install: (install scripts present))
while the advisory listing reports the same tree as clean:
$ npm install-scripts ls
No packages with unreviewed install scripts.
For contrast, when the same core-js is reached through a real dependency edge (so isRegistryDependency=true), the name-only deny matches and npm install succeeds under the strict gate.
Environment
- npm: 12.0.0-pre.1 (
latestbranch), happens in v11 as well - Node.js: v24.17.0
- OS Name: macOS (Darwin 25.5.0)
- System Model Name: Mac17,6
- install strategy: default (hoisted); a workspace is required to retain the orphan
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the buildIdealTree path and the strict preflight that reports unreviewed install scripts, then compare them with the tree walked by npm install-scripts ls. Trace how nested workspace registry nodes are classified for edgesIn, isRegistryDependency, extraneous, and isScriptAllowed. Done means an explicitly denied or prunable orphan no longer gates installation and both commands agree.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, node.js
- Domain
- cli, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100