Is there an existing issue for this?
This issue exists in the latest npm version
This is not just 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 (
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
Is there an existing issue for this?
This issue exists in the latest npm version
This is not just a request to bump a dependency for a CVE
Current Behavior
With
strict-allow-scripts=trueand a name-only deny entry such as"allowScripts": { "esbuild": false },npm installaborts withESTRICTALLOWSCRIPTS, claiming a package has an install script "not covered by allowScripts" — even though that package is explicitly denied by name and isextraneous(no incoming edges, slated for pruning, its install script will never run). The extraneous node exists only as a nestedpackage-lock.jsonentry, not on disk.This reproduces under the default (hoisted) install strategy. The precondition is a registry node nested under a workspace's
node_modulesthat nothing depends on:buildIdealTreeprunes 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 lsreports the tree as fully covered ("No packages with unreviewed install scripts" — it walks the loaded actual tree), while the strict preflight (which walksbuildIdealTree) surfaces the orphan as unreviewed.The bug was first observed with two
esbuildnodes in a workspace monorepo, where one copy is depended on and another is an orphan leftover:truefalsefalse(deny matches — OK)falsetruenull(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
allowScriptsentry, or that is extraneous and will be pruned before any script runs, must not gate the install understrict-allow-scripts. The strict gate andnpm install-scripts lsshould agree on the same node.Steps To Reproduce
Self-contained CLI reproduction.
core-jsstands in foresbuild(zero dependencies, declares apostinstall). The nested orphan lockfile entry is injected to model the stale entry that real workspace installs can leave behind; the bug is thatnpm installthen false-positives on it.Observed:
while the advisory listing reports the same tree as clean:
For contrast, when the same
core-jsis reached through a real dependency edge (soisRegistryDependency=true), the name-only deny matches andnpm installsucceeds under the strict gate.Environment
latestbranch), happens in v11 as well