Skip to content

fix(install): strip npm_config_allow_scripts from the per-client installs - #2713

Merged
cliffhall merged 4 commits into
v2/mainfrom
v2/fix/2518-install-clients-allow-scripts
Oct 13, 2026
Merged

cliffhall merged 4 commits into
v2/mainfrom
v2/fix/2518-install-clients-allow-scripts

Conversation

@cliffhall

Copy link
Copy Markdown
Member

Closes #2518

Cause

The reporter has allow-scripts set in an npmrc (user or global). npm accepts it there, so the root install succeeds. But npm exports every non-default config value, whichever npmrc set it, to lifecycle scripts as npm_config_* (@npmcli/config/lib/set-envs.js). So scripts/install-clients.mjs inherits npm_config_allow_scripts, and the npm install it spawns in clients/web reads that as the CLI/env layer. npm 11.19+ rejects that layer in any project install (lib/utils/resolve-allow-scripts.js → EALLOWSCRIPTS).

This is not specific to macOS. It affects any from-source v2 install on npm ≥ 11.19 with allow-scripts set in an npmrc. npx users never run the cascade.

Fix

  • The per-client installs run with npm_config_allow_scripts removed from their environment, matched case-insensitively as npm matches it. Nothing is lost: each child npm reads the same npmrc files itself, where the setting is allowed.
  • The logic is now an exported childInstallEnv() behind a main guard, so it can be tested. The script ships in the tarball, so it stays self-contained.
  • The main guard compares real paths. Node resolves symlinks for import.meta.url but not for argv[1], so a checkout reached through a symlink (e.g. macOS /var → /private/var) would otherwise skip the cascade silently. The new test hit exactly that.

Tests

scripts/install-clients.test.mjs:

  • Unit cases for childInstallEnv: strips the key in any case, keeps other config, doesn't mutate its input.
  • A premise case against real npm: allow-scripts arriving through the env fails with EALLOWSCRIPTS. It is skipped on npm < 11.19.
  • An end-to-end case that runs the script against a throwaway repo layout. The HOME npmrc sets allow-scripts and the parent-npm env var is present, and the install is offline. I confirmed it fails with the env: line removed and passes with it.

npm run local:gate is green.

Related

#2712 (filed alongside this PR) is the separate question of v2's own install-script policy: denying esbuild/fsevents as v1 did in #2077, plus a guard against unreviewed scripts. This PR doesn't touch policy.

🤖 Generated with Claude Code

…alls (#2518)

npm exports every non-default config value to lifecycle scripts as
npm_config_*, whichever npmrc set it. With allow-scripts in a user's
~/.npmrc, the root install accepted it and then handed it to the
install-clients postinstall as npm_config_allow_scripts; the child
`npm install` read that as --allow-scripts on the command line, which
npm 11.19+ rejects in a project install (EALLOWSCRIPTS), so a from-source
install failed at clients/web.

The child installs now run with that one variable removed. Nothing is
lost: each child npm reads the same npmrc files itself, where the setting
is allowed.

The script's main guard compares real paths, since Node resolves symlinks
for import.meta.url but not for argv[1], so the cascade cannot skip
silently when the checkout is reached through a symlink.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
@cliffhall cliffhall added the v2 Issues and PRs for v2 label Oct 13, 2026
@cliffhall cliffhall linked an issue Oct 13, 2026 that may be closed by this pull request
2 tasks done
@cliffhall
cliffhall requested a balanced review from Copilot October 13, 2026 04:22

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The regression coverage is nondeterministic on CI, and the end-to-end test mishandles Windows paths containing spaces.

2 open findings
What changed in this PR

Fixes nested client installs failing under npm 11.19+ when allow-scripts is inherited from npm configuration.

Changes:

  • Strips inherited npm_config_allow_scripts values before client installs.
  • Adds executable guards and unit/end-to-end regression tests.
File Description
scripts/​install-clients.mjs Sanitizes child environments and adds a real-path main guard.
scripts/​install-clients.test.mjs Tests environment sanitization and npm install behavior.

🧠 Review effort: Balanced

Comment thread scripts/install-clients.mjs Outdated
Comment thread scripts/install-clients.test.mjs
…t a shell (#2518)

The symlink case now launches the fixture through a deliberate symlink
(a junction on Windows) and the plain case runs from a canonical path, so
reverting the realpath guard fails on every platform rather than only
where tmpdir happens to be an alias. Node is spawned without a shell, so
a Windows temp path containing a space is not re-split.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
@cliffhall

Copy link
Copy Markdown
Member Author

Copilot round 1: both findings fixed in e3c76ab (replies in each thread).

  • Symlink guard coverage: a new deterministic symlinked-checkout case. The plain case now runs from a canonical path.
  • Windows shell quoting: Node is spawned without a shell. The shell is kept only for npm.cmd.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The affected npm 11.19+ spawn behavior is not deterministically exercised by the current CI environment.

1 open finding
1 resolved since last review
Previously missed (1)

In code that hasn't changed since last review

Medium severity CI test skips spawn env wiring on bundled npm 10

scripts/​install-clients.test.mjs:114

This version-gated premise leaves the actual spawn wiring unprotected in CI. The build job runs test:scripts on Node 22's bundled npm 10.x (.github/workflows/main.yml:67-96; the workflow confirms that bundle at lines 352-353), so this case is skipped and the end-to-end case below still passes if env: childInstallEnv(process.env) is removed. Add a deterministic wiring test using a stub npm executable that fails when npm_config_allow_scripts is present, or run this suite under npm >=11.19, so the regression fails in CI.

🧠 Review effort: Balanced

… runner's npm (#2518)

CI's Node 22 bundles npm 10, which predates the allow-scripts gate, so the
real-npm cases skip or pass there whether or not the strip is wired in. A
stub npm on PATH fails whenever npm_config_allow_scripts reaches it, so
dropping the env wiring now fails on every runner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
@cliffhall

Copy link
Copy Markdown
Member Author

Copilot round 2:

  • Previously missed — CI test skips spawn env wiring on bundled npm 10 (from the Suppressed/previously-missed block, so there's no thread to reply in): fixed in 1ac6e2f. A stub npm on PATH (npm + npm.cmd) exits 1 with EALLOWSCRIPTS whenever npm_config_allow_scripts reaches it, so it behaves like npm 11.19+ on any runner. The case first checks that the stub rejects the variable, then runs the cascade through it. Verified: removing env: childInstallEnv(process.env) now fails this case, independent of the installed npm.
  • Add deterministic symlink-path coverage: still listed as open, but it is the round-1 thread, already fixed in e3c76ab and answered there.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The main guard still skips symlinked checkouts when --preserve-symlinks-main is enabled.

0 open findings

1 resolved since last review
Previously missed (1)

In code that hasn't changed since last review

Medium severity Canonicalize import.meta.url before symlink-aware entrypoint comparison

scripts/​install-clients.mjs:101

This guard can still silently skip the cascade when the checkout is reached through a symlink and Node is launched with --preserve-symlinks-main (including via NODE_OPTIONS). That flag makes import.meta.url retain the symlink path, while only argv[1] is canonicalized here, so the comparison is false in the exact scenario this guard is intended to support. Canonicalize both paths before comparing them; extending the symlink test to launch Node with this flag would pin the behavior.

🧠 Review effort: Balanced

--preserve-symlinks-main keeps the symlink in import.meta.url, so
canonicalizing only argv[1] still skipped the cascade silently for a
checkout reached through a symlink under that flag. The symlink case now
runs with and without it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
@cliffhall

Copy link
Copy Markdown
Member Author

Copilot round 3:

  • Previously missed — canonicalize import.meta.url before the symlink-aware entrypoint comparison (no thread to reply in): fixed in 996500e. Both sides of the main guard now go through realpathSync, and the symlinked-checkout case also runs under --preserve-symlinks-main. Verified that canonicalizing only argv[1] fails the new variant.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Approval recommended

The fix is narrowly scoped, preserves other npm configuration, and has comprehensive cross-platform regression coverage.

0 open findings

🧠 Review effort: Balanced

@cliffhall

Copy link
Copy Markdown
Member Author

Copilot review closed: round 4 came back clean (no inline comments, no findings in the headline or the suppressed/previously-missed block). All four rounds' findings were fixed. None were declined.

@cliffhall
cliffhall merged commit 90a2791 into v2/main Oct 13, 2026
8 checks passed
@cliffhall
cliffhall deleted the v2/fix/2518-install-clients-allow-scripts branch October 13, 2026 05:13
@cliffhall cliffhall mentioned this pull request Oct 13, 2026
2 tasks done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2 Issues and PRs for v2

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Build errors on MacOS 26.6 (Tahoe)

2 participants