[BUG] strict-allow-scripts false-positives on extraneous (edgeless) registry nodes
BugNeeds TriageallowScripts
### Is there an existing issue for this?
- [x] I have searched the existing issues
### This issue exists in the latest npm version
- [x] I am using the latest npm
### This is not just a request to bump a dependency for a CVE
- [x] 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.
```bash
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 (`latest` branch), 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
5 条评论