Skip to content

fix(install): repeat specs in global allow-scripts suggestion - #9840

Open
Ashish-CodeJourney wants to merge 2 commits into
npm:latestfrom
Ashish-CodeJourney:fix/9835-global-allow-scripts-suggestion
Open

fix(install): repeat specs in global allow-scripts suggestion#9840
Ashish-CodeJourney wants to merge 2 commits into
npm:latestfrom
Ashish-CodeJourney:fix/9835-global-allow-scripts-suggestion

Conversation

@Ashish-CodeJourney

@Ashish-CodeJourney Ashish-CodeJourney commented Aug 4, 2026

Copy link
Copy Markdown

The blocked-install-scripts warning suggested npm install -g --allow-scripts=<pkg>, which has no install targets, so the command falls back to installing the current directory and fails with ENOENT reading package.json for anyone not sitting in a project.

Stop suggesting a command at all: name the flag to add to the install that was already run. Reconstructing the command is not safe — npm.argv carries positional specs only, so flags are silently dropped. Also derive the suggested --allow-scripts values from the policy identity rather than the display name, so git, file and remote deps get a key the matcher accepts.

Fixes: #9835

What / Why

Global installs have no project package.json, so npm install-scripts approve cannot be used. The warning instead points at --allow-scripts, but the command it prints is not runnable:

$ npm install -g esbuild

added 2 packages in 3s
npm warn install-scripts 1 package had install scripts blocked because they are not covered by allowScripts:
npm warn install-scripts   esbuild@0.28.1 (postinstall: node install.js)
npm warn install-scripts
npm warn install-scripts Run `npm install -g --allow-scripts=esbuild` to allow these scripts once, or `npm config set allow-scripts=esbuild --location=user` to allow them for all global installs.

$ npm install -g --allow-scripts=esbuild
npm error code ENOENT
npm error syscall open
npm error path /tmp/tmp.j1pwe1SPOy/package.json
npm error errno -2
npm error enoent Could not read package.json: Error: ENOENT: no such file or directory, open '/tmp/tmp.j1pwe1SPOy/package.json'

npm install -g with no positional specs installs the current directory, which a global installer is generally not sitting in, so the suggested remediation dead-ends on ENOENT.

An earlier revision of this PR rebuilt the command from npm.command and npm.argv. Per review, that is not safe:

  • lib/npm.js:91 sets npm.argv from config.parsedArgv.remain, which is positional specs only. Flags are dropped, so an install from a private registry would be suggested back as a default-registry install that also allows that package's scripts to run.
  • The specs are unquoted, so npm i -g 'pkg@>=1.2.0' turns > into shell redirection.

Review also surfaced a second bug on the same line, present on latest: the suggested --allow-scripts / npm config set allow-scripts values are built from trustedDisplay(node).name, which is a display name. Per workspaces/arborist/lib/script-allowed.js, only registry deps are matched by name — git, file, remote and tarball deps are matched by their resolved source. Following the advice left those scripts blocked. lib/commands/rebuild.js and lib/utils/strict-allow-scripts-preflight.js build their suggestions the same way and have the same bug.

How

remediationLines() in lib/utils/reify-output.js no longer prints a command. It names the flag to add to the install the user already ran, via a new allowScriptsFlag() helper next to the existing configSetAllowScripts() in lib/utils/allow-scripts-remediation.js:

$ npm install -g esbuild
npm warn install-scripts Re-run your install with `--allow-scripts=esbuild` to allow these scripts once, or run `npm config set allow-scripts=esbuild --location=user` to allow them for all global installs.

$ npm install -g esbuild --allow-scripts=esbuild

changed 2 packages in 398ms

Nothing is reconstructed, so no flags can be lost and no spec needs re-quoting into a command.

The values themselves now come from a new shared policyKeyFor(node) in the same module. It builds candidate keys (trusted registry name → node.resolvedresolvedSourceSpecs) and verifies each against the node with the real matches() from script-allowed.js, so the key handed to the user is one the policy accepts rather than one that merely looks right. rebuild.js and strict-allow-scripts-preflight.js use it too. Resolved sources carry shell metacharacters — # in a git committish starts a comment — so the emitted value is quoted when needed.

The same non-working example was documented in npm install-scripts and npm approve-scripts; both are corrected to npm install -g canvas sharp --allow-scripts=canvas,sharp, which is runnable as written.

Tests

Written failing first.

New test/lib/utils/allow-scripts-remediation.js:

  • registry deps are keyed by their trusted name
  • aliased registry deps are keyed by the registered name
  • tarball / file / git deps are keyed by their resolved source
  • falls back to the display name when nothing matches (bundled deps)
  • plain keys are left unquoted; shell-unsafe keys are quoted; single quotes are escaped

test/lib/utils/reify-output.js:

  • global remediation never suggests a spec-less install command
  • global remediation uses resolved sources as policy keys
  • global remediation quotes shell-unsafe policy keys

test/lib/utils/strict-allow-scripts-preflight.js:

  • global error suggests the resolved source for a tarball dep

The two command-replay tests from the earlier revision are removed along with the helper they covered.

Known gap

npm update -g --allow-scripts=<pkg> still will not rerun a blocked script when there is nothing to update — the remediation is only reached on an install that actually reifies the package. Closing that means making approve-scripts work for global installs, which currently throws EGLOBAL at lib/utils/allow-scripts-cmd.js:71. Left out of this PR as a separate change; happy to fold it in if preferred.

References

Fixes #9835

The blocked-install-scripts warning suggested `npm install -g
--allow-scripts=<pkg>`, which has no install targets, so the command
falls back to installing the current directory and fails with ENOENT
reading package.json for anyone not sitting in a project.

Build the suggestion from the command that was actually run and its
positional specs, so `npm install -g esbuild` now suggests `npm install
-g esbuild --allow-scripts=esbuild`. Commands invoked without specs
(`npm update -g`) keep the bare form, which works.

Fixes: npm#9835
@Ashish-CodeJourney
Ashish-CodeJourney requested review from a team as code owners August 4, 2026 11:14
Comment thread lib/utils/allow-scripts-remediation.js Outdated
// suggestion would fail with ENOENT reading package.json.
const globalAllowScripts = (npm, names) => {
const command = npm.command || 'install'
const specs = npm.argv?.length ? ` ${npm.argv.join(' ')}` : ''

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

npm.argv cannot safely reconstruct the original command. It drops flags, so an install from a private registry would be retried against the default registry while explicitly allowing that package's scripts. The values are also unquoted, so a spec like 'pkg@>=1.2.0' turns > into shell redirection. Could we avoid replaying the install and operate on the installed package instead

@Ashish-CodeJourney Ashish-CodeJourney Aug 11, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed, and dropped the replay entirely. lib/npm.js:91 sets npm.argv from
config.parsedArgv.remain, which is positionals only, so an install from a
private registry would have been suggested back as a default-registry install
that also allows that package's scripts to run. The unquoted > in a spec like
pkg@>=1.2.0 is a problem too.

Rather than replaying the install, the warning now names the flag to add to the
install you already ran:

Re-run your install with --allow-scripts=esbuild to allow these scripts once,
or run npm config set allow-scripts=esbuild --location=user to allow them for
all global installs.

No command reconstruction, so no flag loss, and the emitted value is shell-quoted
when it needs to be.

Comment thread test/lib/utils/reify-output.js Outdated
mock.npm.finish()

const warn = mock.logs.warn.byTitle('install-scripts').join('\n')
t.match(warn, /npm update -g --allow-scripts=esbuild/)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This checks the warning text, but not whether the suggested command works. If the first update installed the latest esbuild with its script blocked, running npm update -g --allow-scripts=esbuild finds nothing to update and does not rerun the script. The test should execute the remediation and confirm the script runs.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That test went away with the replay it was covering. The underlying point stands
though: npm update -g --allow-scripts=esbuild finds nothing to update and never
reruns the script, so no suggested install command fully closes this.

Fixing it properly means making approve-scripts work for global installs, which
currently throws EGLOBAL at lib/utils/allow-scripts-cmd.js:71. I kept that out
of this PR since it is a design change rather than a bug fix, and this PR now at
least stops printing a command that cannot work. Happy to take it on here if you
would rather have both together.

Comment thread lib/utils/allow-scripts-remediation.js Outdated
const globalAllowScripts = (npm, names) => {
const command = npm.command || 'install'
const specs = npm.argv?.length ? ` ${npm.argv.join(' ')}` : ''
return `npm ${command} -g${specs} --allow-scripts=${names.join(',')}`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Package names only work as policy keys for registry dependencies. URL, git, and file dependencies are matched by their resolved source, so --allow-scripts=tool would still leave those scripts blocked. This needs the trusted policy identity for each node rather than its display name.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed and it turned out to predate this PR: lib/commands/rebuild.js and
lib/utils/strict-allow-scripts-preflight.js build their npm config set
suggestions from trustedDisplay(node).name too, so they had the same bug on
latest.

All three now go through a shared policyKeyFor(node), which builds candidate
keys (trusted registry name → node.resolvedresolvedSourceSpecs) and
verifies each against the node with the real matches() from
script-allowed.js. That way the key we hand the user is one the matcher
actually accepts, rather than one that looks right: git, file, remote and tarball
deps now get their resolved source. Those carry shell metacharacters — # in a
git committish starts a comment — so the value is quoted when needed.

`npm install -g esbuild` warned "Run `npm install -g
--allow-scripts=esbuild`", which has no specs and so installs the current
directory, failing with ENOENT reading package.json.

The command cannot be reconstructed either: `npm.argv` carries positionals
only, so flags like `--registry` would be dropped from a suggestion that
also allows that package's scripts to run, and unquoted specs such as
`pkg@>=1.2.0` turn `>` into shell redirection. Suggest the flag to add to
the install the user already ran instead of replaying it.

Also derive the suggested policy keys with the real matcher rather than the
display name. Only registry deps are matched by name; git, file, remote and
tarball deps are matched by their resolved source, so the previous
suggestions left those scripts blocked. Resolved sources contain shell
metacharacters, so the value is quoted when needed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Incomplete remediate prompt npm install -g --allow-scripts=<package>

2 participants