From 609cb24e564cb0bf9659f96444aba46210715821 Mon Sep 17 00:00:00 2001 From: Fnine59 Date: Tue, 1 Sep 2026 02:48:45 +0800 Subject: [PATCH] fix(config): avoid exporting persistent allow-scripts (#9913) ## What / Why A user or global `.npmrc` can define `allow-scripts` as persistent policy. `setEnvs()` currently carries that non-default value into lifecycle child processes as `npm_config_allow_scripts`. If a lifecycle script runs a nested project-scoped `npm install`, the inner process treats the inherited value as an environment override and rejects it with `EALLOWSCRIPTS` instead of reloading the policy from its persistent config source. Mark `allow-scripts` as non-exportable. This only prevents `setEnvs()` from synthesizing the lifecycle environment variable; it does not remove an explicitly supplied environment value or change how the outer command reads its config. Pacote's git-preparation environment filtering and #9783 are outside this change. The regression test models a user-level value in the inherited config chain and verifies that lifecycle scripts do not receive `npm_config_allow_scripts`. ## AI assistance OpenAI Codex assisted with analysis, implementation, and test design. The patch was verified with the focused regression, the complete `@npmcli/config` suite, lint, and template checks. ## References Fixes #9912 (cherry picked from commit b016aa287d01fcd688d6704222f193ce5c04bfb7) --- tap-snapshots/test/lib/docs.js.test.cjs | 2 +- test/lib/utils/resolve-allow-scripts.js | 16 +++++++++ .../config/lib/definitions/definitions.js | 1 + workspaces/config/test/set-envs.js | 36 +++++++++++++++++++ 4 files changed, 54 insertions(+), 1 deletion(-) diff --git a/tap-snapshots/test/lib/docs.js.test.cjs b/tap-snapshots/test/lib/docs.js.test.cjs index 072d6619a95d1..29c522dddec60 100644 --- a/tap-snapshots/test/lib/docs.js.test.cjs +++ b/tap-snapshots/test/lib/docs.js.test.cjs @@ -327,7 +327,7 @@ Each name is matched against a dependency's resolved identity, not against the package's self-reported name. \`--ignore-scripts\` and \`--dangerously-allow-all-scripts\` both override this setting. - +This value is not exported to the environment for child processes. #### \`allow-scripts-pending\` diff --git a/test/lib/utils/resolve-allow-scripts.js b/test/lib/utils/resolve-allow-scripts.js index a27d600d98f04..650094ba170bd 100644 --- a/test/lib/utils/resolve-allow-scripts.js +++ b/test/lib/utils/resolve-allow-scripts.js @@ -86,6 +86,22 @@ t.test('--allow-scripts CLI flag is rejected in project-scoped installs', async ) }) +t.test('allow-scripts environment policy is rejected in project-scoped installs', async t => { + const mock = await mockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ name: 'p' }), + }, + globals: { + 'process.env.npm_config_allow_scripts': 'canvas', + }, + }) + const resolveAllowScripts = loadResolver(t) + await t.rejects( + resolveAllowScripts(mock.npm), + { code: 'EALLOWSCRIPTS', message: /--allow-scripts is not allowed/ } + ) +}) + t.test('--allow-scripts CLI flag is accepted in global installs (RFC layer 1 wins)', async t => { const mock = await mockNpm(t, { prefixDir: { diff --git a/workspaces/config/lib/definitions/definitions.js b/workspaces/config/lib/definitions/definitions.js index 2bb1713458af9..4d07ff67ab040 100644 --- a/workspaces/config/lib/definitions/definitions.js +++ b/workspaces/config/lib/definitions/definitions.js @@ -258,6 +258,7 @@ const definitions = { default: '', type: [String, Array], hint: '', + envExport: false, description: ` Comma-separated list of packages whose install-time lifecycle scripts (\`preinstall\`, \`install\`, \`postinstall\`, and \`prepare\` for diff --git a/workspaces/config/test/set-envs.js b/workspaces/config/test/set-envs.js index c7af0faca33c0..942626531eccc 100644 --- a/workspaces/config/test/set-envs.js +++ b/workspaces/config/test/set-envs.js @@ -241,3 +241,39 @@ t.test('dont set configs marked as envExport:false', t => { t.strictSame(env, { ...extras }, 'not exported, because envExport=false') t.end() }) + +t.test('does not export persistent allow-scripts config', t => { + const { definitions, defaults } = mockDefinitions(t) + const userConf = Object.create(defaults) + userConf['allow-scripts'] = 'canvas' + const envConf = Object.create(userConf) + const cliConf = Object.create(envConf) + const env = {} + const config = { + list: [cliConf, envConf], + env, + defaults, + definitions, + execPath, + globalPrefix, + localPrefix, + npmPath, + npmBin, + } + + setEnvs(config) + t.equal( + env.npm_config_allow_scripts, + undefined, + 'persistent policy is reloaded instead of exported to lifecycle scripts' + ) + envConf['allow-scripts'] = 'sharp' + env.npm_config_allow_scripts = 'sharp' + setEnvs(config) + t.equal( + env.npm_config_allow_scripts, + 'sharp', + 'an explicit environment policy remains inherited' + ) + t.end() +})