From dcceb5d294c5fe0462f763e3d93dd5b2e904c5e7 Mon Sep 17 00:00:00 2001 From: Jamie Magee Date: Tue, 2 Jun 2026 11:50:01 -0700 Subject: [PATCH 1/4] fix: point global installs at allow-scripts instead of approve-scripts in unreviewed-scripts warnings --- .../content/commands/npm-approve-scripts.md | 8 +++++ lib/commands/rebuild.js | 9 ++++- lib/utils/reify-output.js | 25 +++++++++++++- lib/utils/strict-allow-scripts-preflight.js | 16 +++++++-- test/lib/commands/rebuild.js | 25 ++++++++++++++ test/lib/utils/reify-output.js | 33 +++++++++++++++++++ .../utils/strict-allow-scripts-preflight.js | 30 +++++++++++++++++ 7 files changed, 141 insertions(+), 5 deletions(-) diff --git a/docs/lib/content/commands/npm-approve-scripts.md b/docs/lib/content/commands/npm-approve-scripts.md index 70abe153369d6..1eaf096397a1d 100644 --- a/docs/lib/content/commands/npm-approve-scripts.md +++ b/docs/lib/content/commands/npm-approve-scripts.md @@ -19,6 +19,14 @@ In the current release, this field is advisory: install scripts still run by default, but installs print a list of packages whose scripts have not been reviewed. A future release will block unreviewed install scripts. +This command only works inside a project that has a `package.json`. It does +not apply to global installs (`npm install -g`) or one-off executions +(`npm exec` / `npx`), which have no project `package.json` to write to and +will fail with an `EGLOBAL` error. To allow install scripts in those +contexts, use the `--allow-scripts` flag at install time (for example +`npm install -g --allow-scripts=canvas,sharp`) or set the `allow-scripts` +key in your user or global `.npmrc`. + There are three modes: ```bash diff --git a/lib/commands/rebuild.js b/lib/commands/rebuild.js index 333a879026cbc..308813b1d0c63 100644 --- a/lib/commands/rebuild.js +++ b/lib/commands/rebuild.js @@ -73,10 +73,17 @@ class Rebuild extends ArboristWorkspaceCmd { if (unreviewed.length > 0) { const count = unreviewed.length const noun = count === 1 ? 'package has' : 'packages have' + // `npm approve-scripts` writes to a project package.json, which doesn't + // exist for global rebuilds. Point global users at the mechanism that + // works there: the `allow-scripts` setting in their user/global .npmrc. + const remediation = this.npm.global + ? 'Add the packages to the `allow-scripts` setting in your user or ' + + 'global .npmrc to allow their scripts.' + : 'Run `npm approve-scripts --allow-scripts-pending` to review.' log.warn( 'rebuild', `${count} ${noun} install scripts not yet covered by allowScripts. ` + - 'Run `npm approve-scripts --allow-scripts-pending` to review.' + remediation ) } diff --git a/lib/utils/reify-output.js b/lib/utils/reify-output.js index b1e1ffbcddd17..50bd3d69f7fdd 100644 --- a/lib/utils/reify-output.js +++ b/lib/utils/reify-output.js @@ -243,10 +243,14 @@ const unreviewedScriptsMessage = (npm, unreviewedScripts) => { const pkg = count === 1 ? 'package has' : 'packages have' const header = `${count} ${pkg} install scripts not yet covered by allowScripts:` + const names = [] const lines = unreviewedScripts.map(({ node, scripts }) => { const { name, version } = trustedDisplay(node) /* istanbul ignore next: every test node has a name */ const display = name || '' + if (name) { + names.push(name) + } const ver = version ? `@${version}` : '' const events = Object.entries(scripts) .map(([event, cmd]) => `${event}: ${cmd}`) @@ -260,9 +264,28 @@ const unreviewedScriptsMessage = (npm, unreviewedScripts) => { header, ...lines, '', - 'Run `npm approve-scripts --allow-scripts-pending` to review, or `npm approve-scripts ` to allow.', + ...remediationLines(npm, names), ].join('\n') ) } +// `npm approve-scripts` writes to a project package.json, which doesn't +// exist for global installs (it throws EGLOBAL). For those, point users at +// the mechanism that does work globally: the `--allow-scripts` flag and the +// `allow-scripts` setting in their user/global .npmrc. +const remediationLines = (npm, names) => { + if (npm.global) { + const list = names.length ? names.join(',') : '' + return [ + `Run \`npm install -g --allow-scripts=${list}\` to allow these scripts, ` + + 'or add the packages to the `allow-scripts` setting in your user or ' + + 'global .npmrc.', + ] + } + return [ + 'Run `npm approve-scripts --allow-scripts-pending` to review, ' + + 'or `npm approve-scripts ` to allow.', + ] +} + module.exports = reifyOutput diff --git a/lib/utils/strict-allow-scripts-preflight.js b/lib/utils/strict-allow-scripts-preflight.js index a3f83ea4b662b..8362b4d186176 100644 --- a/lib/utils/strict-allow-scripts-preflight.js +++ b/lib/utils/strict-allow-scripts-preflight.js @@ -46,13 +46,23 @@ const strictAllowScriptsPreflight = async ({ arb, npm, idealTreeOpts }) => { return ` ${label} (${events})` }).join('\n') + // `npm approve-scripts` / `npm deny-scripts` write to a project + // package.json, which doesn't exist for global installs. Point global + // users at the `--allow-scripts` flag and the `allow-scripts` .npmrc + // setting, which both work for global installs. + const remediation = npm.global + ? 'Allow them with `--allow-scripts`, add them to the `allow-scripts` ' + + 'setting in your user or global .npmrc, or bypass this check with ' + + '`--dangerously-allow-all-scripts`.' + : 'Approve them with `npm approve-scripts`, deny them with ' + + '`npm deny-scripts`, or bypass this check with ' + + '`--dangerously-allow-all-scripts`.' + throw Object.assign( new Error( `--strict-allow-scripts: ${unreviewed.length} package(s) have install ` + `scripts not covered by allowScripts:\n${lines}\n` + - 'Approve them with `npm approve-scripts`, deny them with ' + - '`npm deny-scripts`, or bypass this check with ' + - '`--dangerously-allow-all-scripts`.' + remediation ), { code: 'ESTRICTALLOWSCRIPTS' } ) diff --git a/test/lib/commands/rebuild.js b/test/lib/commands/rebuild.js index de91fd3471b4e..771a48f1418b4 100644 --- a/test/lib/commands/rebuild.js +++ b/test/lib/commands/rebuild.js @@ -244,6 +244,31 @@ t.test('emits Phase 1 advisory warning for unreviewed install scripts', async t ) }) +t.test('global advisory warning points at .npmrc, not approve-scripts', async t => { + const { npm, logs } = await setupMockNpm(t, { + config: { + global: true, + }, + globalPrefixDir: { + node_modules: { + canvas: { + 'index.js': '', + 'package.json': JSON.stringify({ + name: 'canvas', + version: '1.0.0', + scripts: { install: 'echo install' }, + }), + }, + }, + }, + }) + await npm.exec('rebuild', []) + const warn = logs.warn.byTitle('rebuild').join('\n') + t.match(warn, /install scripts not yet covered by allowScripts/) + t.match(warn, /allow-scripts.*\.npmrc/s) + t.notMatch(warn, /approve-scripts/) +}) + t.test('no advisory warning when allowScripts covers the package', async t => { const { npm, logs } = await setupMockNpm(t, { prefixDir: { diff --git a/test/lib/utils/reify-output.js b/test/lib/utils/reify-output.js index b1bc92b1c77ae..75d175edbcb85 100644 --- a/test/lib/utils/reify-output.js +++ b/test/lib/utils/reify-output.js @@ -487,6 +487,39 @@ t.test('prints unreviewed install scripts summary', async t => { t.match(warn, /npm approve-scripts --allow-scripts-pending/) }) +t.test('global install suggests --allow-scripts, not approve-scripts', async t => { + const mockReifyWithExtras = async (t, reify, extras, config = {}) => { + const mock = await mockNpm(t, { config }) + reifyOutput(mock.npm, reify, extras) + mock.npm.finish() + return mock + } + + const baseReify = { + actualTree: { name: 'host', inventory: { has: () => false } }, + diff: { children: [] }, + } + + const unreviewedScripts = [ + { + node: { packageName: 'canvas', name: 'canvas', version: '2.11.0', path: '/x/canvas' }, + scripts: { install: 'node-gyp rebuild' }, + }, + { + node: { packageName: 'sharp', name: 'sharp', version: '0.33.2', path: '/x/sharp' }, + scripts: { preinstall: 'pre', postinstall: 'post' }, + }, + ] + + const mock = await mockReifyWithExtras(t, baseReify, { unreviewedScripts }, { global: true }) + const warn = mock.logs.warn.byTitle('allow-scripts').join('\n') + t.match(warn, /2 packages have install scripts not yet covered/) + t.match(warn, /canvas@2\.11\.0 \(install: node-gyp rebuild\)/) + t.match(warn, /npm install -g --allow-scripts=canvas,sharp/) + t.match(warn, /allow-scripts.*\.npmrc/s) + t.notMatch(warn, /approve-scripts/) +}) + t.test('single unreviewed script uses singular wording', async t => { const mockReifyWithExtras = async (t, reify, extras) => { const mock = await mockNpm(t, {}) diff --git a/test/lib/utils/strict-allow-scripts-preflight.js b/test/lib/utils/strict-allow-scripts-preflight.js index e246c68998c45..392dab6d9d884 100644 --- a/test/lib/utils/strict-allow-scripts-preflight.js +++ b/test/lib/utils/strict-allow-scripts-preflight.js @@ -189,3 +189,33 @@ t.test('error label falls back to node.name when package.version is missing', as { message: /no-version-pkg \(install: node-gyp rebuild\)/ } ) }) + +t.test('project-scoped error suggests approve-scripts / deny-scripts', async t => { + const arb = makeArb({ ideal: tree([node({ name: 'canvas' })]) }) + await t.rejects( + preflight({ + arb, + npm: { flatOptions: { strictAllowScripts: true } }, + idealTreeOpts: {}, + }), + { message: /Approve them with `npm approve-scripts`/ } + ) +}) + +t.test('global error points at --allow-scripts, not approve-scripts', async t => { + const arb = makeArb({ ideal: tree([node({ name: 'canvas' })]) }) + await t.rejects( + preflight({ + arb, + npm: { global: true, flatOptions: { strictAllowScripts: true } }, + idealTreeOpts: {}, + }), + (err) => { + t.equal(err.code, 'ESTRICTALLOWSCRIPTS') + t.match(err.message, /--allow-scripts/) + t.match(err.message, /\.npmrc/) + t.notMatch(err.message, /approve-scripts/) + return true + } + ) +}) From baae5859dd4605e9f80d0421db97989e8e47b3f5 Mon Sep 17 00:00:00 2001 From: Jamie Magee Date: Tue, 2 Jun 2026 12:04:34 -0700 Subject: [PATCH 2/4] fix: drop unreachable fallback branches in unreviewed-scripts remediation --- lib/utils/reify-output.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/utils/reify-output.js b/lib/utils/reify-output.js index 50bd3d69f7fdd..5fe33eff7cd0a 100644 --- a/lib/utils/reify-output.js +++ b/lib/utils/reify-output.js @@ -248,9 +248,7 @@ const unreviewedScriptsMessage = (npm, unreviewedScripts) => { const { name, version } = trustedDisplay(node) /* istanbul ignore next: every test node has a name */ const display = name || '' - if (name) { - names.push(name) - } + names.push(display) const ver = version ? `@${version}` : '' const events = Object.entries(scripts) .map(([event, cmd]) => `${event}: ${cmd}`) @@ -275,7 +273,7 @@ const unreviewedScriptsMessage = (npm, unreviewedScripts) => { // `allow-scripts` setting in their user/global .npmrc. const remediationLines = (npm, names) => { if (npm.global) { - const list = names.length ? names.join(',') : '' + const list = names.join(',') return [ `Run \`npm install -g --allow-scripts=${list}\` to allow these scripts, ` + 'or add the packages to the `allow-scripts` setting in your user or ' + From 97bf6e7173defe90936c4a3153279708618b5659 Mon Sep 17 00:00:00 2001 From: Jamie Magee Date: Tue, 2 Jun 2026 16:56:15 -0700 Subject: [PATCH 3/4] fix: suggest npm config set allow-scripts for global remediation --- docs/lib/content/commands/npm-approve-scripts.md | 4 ++-- lib/commands/rebuild.js | 10 ++++++---- lib/utils/reify-output.js | 10 +++++----- lib/utils/strict-allow-scripts-preflight.js | 11 ++++++----- test/lib/commands/rebuild.js | 4 ++-- test/lib/utils/reify-output.js | 2 +- test/lib/utils/strict-allow-scripts-preflight.js | 2 +- 7 files changed, 23 insertions(+), 20 deletions(-) diff --git a/docs/lib/content/commands/npm-approve-scripts.md b/docs/lib/content/commands/npm-approve-scripts.md index 1eaf096397a1d..bb813610b0f47 100644 --- a/docs/lib/content/commands/npm-approve-scripts.md +++ b/docs/lib/content/commands/npm-approve-scripts.md @@ -24,8 +24,8 @@ not apply to global installs (`npm install -g`) or one-off executions (`npm exec` / `npx`), which have no project `package.json` to write to and will fail with an `EGLOBAL` error. To allow install scripts in those contexts, use the `--allow-scripts` flag at install time (for example -`npm install -g --allow-scripts=canvas,sharp`) or set the `allow-scripts` -key in your user or global `.npmrc`. +`npm install -g --allow-scripts=canvas,sharp`) or persist the setting with +`npm config set allow-scripts=canvas,sharp`. There are three modes: diff --git a/lib/commands/rebuild.js b/lib/commands/rebuild.js index 308813b1d0c63..37dfc00044c20 100644 --- a/lib/commands/rebuild.js +++ b/lib/commands/rebuild.js @@ -2,6 +2,7 @@ const { resolve } = require('node:path') const { log, output } = require('proc-log') const npa = require('npm-package-arg') const semver = require('semver') +const { trustedDisplay } = require('@npmcli/arborist/lib/script-allowed.js') const ArboristWorkspaceCmd = require('../arborist-cmd.js') const checkAllowScripts = require('../utils/check-allow-scripts.js') const resolveAllowScripts = require('../utils/resolve-allow-scripts.js') @@ -74,11 +75,12 @@ class Rebuild extends ArboristWorkspaceCmd { const count = unreviewed.length const noun = count === 1 ? 'package has' : 'packages have' // `npm approve-scripts` writes to a project package.json, which doesn't - // exist for global rebuilds. Point global users at the mechanism that - // works there: the `allow-scripts` setting in their user/global .npmrc. + // exist for global rebuilds. Point global users at `npm config set`, + // which writes the `allow-scripts` setting to their user .npmrc. + const names = unreviewed.map(({ node }) => trustedDisplay(node).name) const remediation = this.npm.global - ? 'Add the packages to the `allow-scripts` setting in your user or ' + - 'global .npmrc to allow their scripts.' + ? `Run \`npm config set allow-scripts=${names.join(',')}\` to allow ` + + 'their scripts.' : 'Run `npm approve-scripts --allow-scripts-pending` to review.' log.warn( 'rebuild', diff --git a/lib/utils/reify-output.js b/lib/utils/reify-output.js index 5fe33eff7cd0a..ae942c6645abb 100644 --- a/lib/utils/reify-output.js +++ b/lib/utils/reify-output.js @@ -269,15 +269,15 @@ const unreviewedScriptsMessage = (npm, unreviewedScripts) => { // `npm approve-scripts` writes to a project package.json, which doesn't // exist for global installs (it throws EGLOBAL). For those, point users at -// the mechanism that does work globally: the `--allow-scripts` flag and the -// `allow-scripts` setting in their user/global .npmrc. +// the mechanism that does work globally: the `--allow-scripts` flag for a +// one-off, or `npm config set allow-scripts` to persist it. const remediationLines = (npm, names) => { if (npm.global) { const list = names.join(',') return [ - `Run \`npm install -g --allow-scripts=${list}\` to allow these scripts, ` + - 'or add the packages to the `allow-scripts` setting in your user or ' + - 'global .npmrc.', + `Run \`npm install -g --allow-scripts=${list}\` to allow these scripts ` + + `once, or \`npm config set allow-scripts=${list}\` to allow them for ` + + 'all global installs.', ] } return [ diff --git a/lib/utils/strict-allow-scripts-preflight.js b/lib/utils/strict-allow-scripts-preflight.js index 8362b4d186176..be8cd9b022067 100644 --- a/lib/utils/strict-allow-scripts-preflight.js +++ b/lib/utils/strict-allow-scripts-preflight.js @@ -48,12 +48,13 @@ const strictAllowScriptsPreflight = async ({ arb, npm, idealTreeOpts }) => { // `npm approve-scripts` / `npm deny-scripts` write to a project // package.json, which doesn't exist for global installs. Point global - // users at the `--allow-scripts` flag and the `allow-scripts` .npmrc - // setting, which both work for global installs. + // users at the `--allow-scripts` flag and `npm config set allow-scripts`, + // which both work for global installs. + const names = unreviewed.map(({ node }) => node.package?.name || node.name) const remediation = npm.global - ? 'Allow them with `--allow-scripts`, add them to the `allow-scripts` ' + - 'setting in your user or global .npmrc, or bypass this check with ' + - '`--dangerously-allow-all-scripts`.' + ? 'Allow them with `--allow-scripts`, persist them with ' + + `\`npm config set allow-scripts=${names.join(',')}\`, or bypass this ` + + 'check with `--dangerously-allow-all-scripts`.' : 'Approve them with `npm approve-scripts`, deny them with ' + '`npm deny-scripts`, or bypass this check with ' + '`--dangerously-allow-all-scripts`.' diff --git a/test/lib/commands/rebuild.js b/test/lib/commands/rebuild.js index 771a48f1418b4..bd28733b32ee7 100644 --- a/test/lib/commands/rebuild.js +++ b/test/lib/commands/rebuild.js @@ -244,7 +244,7 @@ t.test('emits Phase 1 advisory warning for unreviewed install scripts', async t ) }) -t.test('global advisory warning points at .npmrc, not approve-scripts', async t => { +t.test('global advisory warning points at npm config set, not approve-scripts', async t => { const { npm, logs } = await setupMockNpm(t, { config: { global: true, @@ -265,7 +265,7 @@ t.test('global advisory warning points at .npmrc, not approve-scripts', async t await npm.exec('rebuild', []) const warn = logs.warn.byTitle('rebuild').join('\n') t.match(warn, /install scripts not yet covered by allowScripts/) - t.match(warn, /allow-scripts.*\.npmrc/s) + t.match(warn, /npm config set allow-scripts=canvas/) t.notMatch(warn, /approve-scripts/) }) diff --git a/test/lib/utils/reify-output.js b/test/lib/utils/reify-output.js index 75d175edbcb85..597a03374815b 100644 --- a/test/lib/utils/reify-output.js +++ b/test/lib/utils/reify-output.js @@ -516,7 +516,7 @@ t.test('global install suggests --allow-scripts, not approve-scripts', async t = t.match(warn, /2 packages have install scripts not yet covered/) t.match(warn, /canvas@2\.11\.0 \(install: node-gyp rebuild\)/) t.match(warn, /npm install -g --allow-scripts=canvas,sharp/) - t.match(warn, /allow-scripts.*\.npmrc/s) + t.match(warn, /npm config set allow-scripts=canvas,sharp/) t.notMatch(warn, /approve-scripts/) }) diff --git a/test/lib/utils/strict-allow-scripts-preflight.js b/test/lib/utils/strict-allow-scripts-preflight.js index 392dab6d9d884..e0c54105f8863 100644 --- a/test/lib/utils/strict-allow-scripts-preflight.js +++ b/test/lib/utils/strict-allow-scripts-preflight.js @@ -213,7 +213,7 @@ t.test('global error points at --allow-scripts, not approve-scripts', async t => (err) => { t.equal(err.code, 'ESTRICTALLOWSCRIPTS') t.match(err.message, /--allow-scripts/) - t.match(err.message, /\.npmrc/) + t.match(err.message, /npm config set allow-scripts=canvas/) t.notMatch(err.message, /approve-scripts/) return true } From c4f8322b06c1ab8710985639e5a7f6b1ac20a41f Mon Sep 17 00:00:00 2001 From: Jamie Magee Date: Wed, 3 Jun 2026 10:49:34 -0700 Subject: [PATCH 4/4] fix: write global allow-scripts config to user .npmrc via shared helper --- docs/lib/content/commands/npm-approve-scripts.md | 2 +- lib/commands/rebuild.js | 4 ++-- lib/utils/allow-scripts-remediation.js | 9 +++++++++ lib/utils/reify-output.js | 3 ++- lib/utils/strict-allow-scripts-preflight.js | 10 +++++++--- 5 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 lib/utils/allow-scripts-remediation.js diff --git a/docs/lib/content/commands/npm-approve-scripts.md b/docs/lib/content/commands/npm-approve-scripts.md index bb813610b0f47..8961b836a75a7 100644 --- a/docs/lib/content/commands/npm-approve-scripts.md +++ b/docs/lib/content/commands/npm-approve-scripts.md @@ -25,7 +25,7 @@ not apply to global installs (`npm install -g`) or one-off executions will fail with an `EGLOBAL` error. To allow install scripts in those contexts, use the `--allow-scripts` flag at install time (for example `npm install -g --allow-scripts=canvas,sharp`) or persist the setting with -`npm config set allow-scripts=canvas,sharp`. +`npm config set allow-scripts=canvas,sharp --location=user`. There are three modes: diff --git a/lib/commands/rebuild.js b/lib/commands/rebuild.js index 37dfc00044c20..16100cc101efa 100644 --- a/lib/commands/rebuild.js +++ b/lib/commands/rebuild.js @@ -7,6 +7,7 @@ const ArboristWorkspaceCmd = require('../arborist-cmd.js') const checkAllowScripts = require('../utils/check-allow-scripts.js') const resolveAllowScripts = require('../utils/resolve-allow-scripts.js') const strictAllowScriptsPreflight = require('../utils/strict-allow-scripts-preflight.js') +const { configSetAllowScripts } = require('../utils/allow-scripts-remediation.js') class Rebuild extends ArboristWorkspaceCmd { static description = 'Rebuild a package' @@ -79,8 +80,7 @@ class Rebuild extends ArboristWorkspaceCmd { // which writes the `allow-scripts` setting to their user .npmrc. const names = unreviewed.map(({ node }) => trustedDisplay(node).name) const remediation = this.npm.global - ? `Run \`npm config set allow-scripts=${names.join(',')}\` to allow ` + - 'their scripts.' + ? `Run \`${configSetAllowScripts(names)}\` to allow their scripts.` : 'Run `npm approve-scripts --allow-scripts-pending` to review.' log.warn( 'rebuild', diff --git a/lib/utils/allow-scripts-remediation.js b/lib/utils/allow-scripts-remediation.js new file mode 100644 index 0000000000000..ff8c9b75a81fe --- /dev/null +++ b/lib/utils/allow-scripts-remediation.js @@ -0,0 +1,9 @@ +// Builds the `npm config set allow-scripts` command suggested to global +// users, who have no project package.json for `npm approve-scripts` to +// write to. `--location=user` keeps the setting in the user .npmrc instead +// of trying (and, for global installs, failing) to write it to the local +// project config. +const configSetAllowScripts = (names) => + `npm config set allow-scripts=${names.join(',')} --location=user` + +module.exports = { configSetAllowScripts } diff --git a/lib/utils/reify-output.js b/lib/utils/reify-output.js index ae942c6645abb..1ccbd4d0cad71 100644 --- a/lib/utils/reify-output.js +++ b/lib/utils/reify-output.js @@ -16,6 +16,7 @@ const npmAuditReport = require('npm-audit-report') const { readTree: getFundingInfo } = require('libnpmfund') const { trustedDisplay } = require('@npmcli/arborist/lib/script-allowed.js') const auditError = require('./audit-error.js') +const { configSetAllowScripts } = require('./allow-scripts-remediation.js') const reifyOutput = (npm, arb, extras = {}) => { const { diff, actualTree } = arb @@ -276,7 +277,7 @@ const remediationLines = (npm, names) => { const list = names.join(',') return [ `Run \`npm install -g --allow-scripts=${list}\` to allow these scripts ` + - `once, or \`npm config set allow-scripts=${list}\` to allow them for ` + + `once, or \`${configSetAllowScripts(names)}\` to allow them for ` + 'all global installs.', ] } diff --git a/lib/utils/strict-allow-scripts-preflight.js b/lib/utils/strict-allow-scripts-preflight.js index be8cd9b022067..d3575289fa8ed 100644 --- a/lib/utils/strict-allow-scripts-preflight.js +++ b/lib/utils/strict-allow-scripts-preflight.js @@ -1,4 +1,6 @@ const checkAllowScripts = require('./check-allow-scripts.js') +const { trustedDisplay } = require('@npmcli/arborist/lib/script-allowed.js') +const { configSetAllowScripts } = require('./allow-scripts-remediation.js') // Pre-flight check for `--strict-allow-scripts`. Call after arborist has // been constructed but before `arb.reify()` runs, so that install scripts @@ -49,11 +51,13 @@ const strictAllowScriptsPreflight = async ({ arb, npm, idealTreeOpts }) => { // `npm approve-scripts` / `npm deny-scripts` write to a project // package.json, which doesn't exist for global installs. Point global // users at the `--allow-scripts` flag and `npm config set allow-scripts`, - // which both work for global installs. - const names = unreviewed.map(({ node }) => node.package?.name || node.name) + // which both work for global installs. Use the trusted display identity + // so the suggested `npm config set` value matches what the policy matches + // on, not the tarball's self-reported name. + const names = unreviewed.map(({ node }) => trustedDisplay(node).name) const remediation = npm.global ? 'Allow them with `--allow-scripts`, persist them with ' + - `\`npm config set allow-scripts=${names.join(',')}\`, or bypass this ` + + `\`${configSetAllowScripts(names)}\`, or bypass this ` + 'check with `--dangerously-allow-all-scripts`.' : 'Approve them with `npm approve-scripts`, deny them with ' + '`npm deny-scripts`, or bypass this check with ' +