Skip to content

fix(powershell): preserve shim semantics without evaluating paths - #10058

Draft
martinrrm wants to merge 9 commits into
latestfrom
martinrrm/fix-powershell-prefix-injection
Draft

martinrrm wants to merge 9 commits into
latestfrom
martinrrm/fix-powershell-prefix-injection

Conversation

@martinrrm

@martinrrm martinrrm commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Prevent the PowerShell npm and npx shims from interpolating the selected Node executable or CLI path into evaluated source.
  • Reparse the user's original PowerShell argument syntax through a script block that references executable paths only through variables, preserving quotes, environment expansion, backtick escaping, pipelines, and redirection.
  • Run the script block in the wrapper scope so the native process's exact exit code is preserved for both npm and npx.
  • Remove project-level prefix before project configuration is loaded, so a repository .npmrc cannot redirect CLI selection.

Regression coverage

  • Paths containing PowerShell subexpressions and single quotes are not evaluated.
  • Single-quoted arguments, environment variables, backtick escapes, pipelines, and stderr redirection retain PowerShell semantics.
  • npm and npx preserve a native exit status of 7.
  • Project prefix is logged as prohibited but cannot affect the effective prefix.

Validation

  • Full @npmcli/config tests, lint, and postlint pass locally.
  • Focused Windows shim tests and root lint pass locally; PowerShell runtime cases are skipped on Linux.
  • Windows PowerShell and PowerShell 7 coverage is running in CI.

Refs github/npm#15799, finding CLI-CROW-CMD-002.

Draft intentionally while the refreshed cross-platform CI matrix completes.

@martinrrm
martinrrm marked this pull request as ready for review October 1, 2026 18:18
@martinrrm
martinrrm requested a review from a team as a code owner October 1, 2026 18:18
@martinrrm
martinrrm marked this pull request as draft October 1, 2026 18:18
@martinrrm martinrrm closed this Oct 28, 2026
@martinrrm
martinrrm force-pushed the martinrrm/fix-powershell-prefix-injection branch from b2fca96 to 51c2bf8 Compare October 28, 2026 18:20
@martinrrm martinrrm reopened this Oct 28, 2026
@martinrrm martinrrm changed the title fix(powershell): avoid evaluating CLI paths as source fix(powershell): preserve shim semantics without evaluating paths Oct 28, 2026
@martinrrm
martinrrm requested a max review from Copilot November 3, 2026 19:12
martinrrm and others added 3 commits November 3, 2026 19:19
Launch the CLI through a native process instead of evaluating executable and CLI paths as PowerShell source, and reject project-level prefix settings so repository config cannot redirect CLI selection.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 3e53db77-0fe4-4320-8ada-f2962ddd303c
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 3e53db77-0fe4-4320-8ada-f2962ddd303c

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

Expanded configuration keys and project-controlled user/global config paths still bypass prefix filtering, allowing repositories to redirect CLI selection.

3 open issues
What changed in this PR

This PR hardens npm and npx’s PowerShell launchers by keeping executable paths out of evaluated source while preserving argument syntax and native exit codes. It also filters direct project-level prefix settings during configuration loading.

Changes:

  • Replace Invoke-Expression with variable-based script blocks executed in the wrapper scope.
  • Ignore direct project prefix entries while retaining the prohibited-setting error.
  • Add regression coverage for special-character paths, PowerShell syntax, exit codes, and prefix precedence.
File Description
workspaces/​config/​test/​index.js Assert that project prefix settings do not affect configuration precedence.
workspaces/​config/​lib/​index.js Remove direct project prefix entries before loading configuration.
test/​bin/​windows-shims.js Expand shim regression cases and update PowerShell process invocation.
bin/​npx.ps1 Invoke npx through a script block that references paths via variables.
bin/​npm.ps1 Invoke npm through a script block that references paths via variables.

Review effort: Max

Comment thread workspaces/config/lib/index.js Resolved
Comment thread workspaces/config/lib/index.js Resolved
Comment thread workspaces/config/lib/index.js Resolved
Ignore project configuration when selecting the global CLI. Reject expanded project prefix keys while preserving raw entries for config saves. Isolate the Windows test environment and cover prefix sources, argument syntax, entry modes, and native exit codes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8a0c2316-9385-4f0a-931e-cb9c15c425c5
@martinrrm
martinrrm force-pushed the martinrrm/fix-powershell-prefix-injection branch from 70d7cc4 to 0d07dc1 Compare November 3, 2026 20:16
martinrrm and others added 2 commits November 3, 2026 20:46
Compare saved INI data instead of assuming LF line endings. Read one complete piped input line without waiting for EOF under coverage, and bound those subprocess checks so failures report promptly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8a0c2316-9385-4f0a-931e-cb9c15c425c5
Use controlled native CLI fixtures to verify npm and npx shim input and argument forwarding without npm exec or run-script consuming stdin. Bound the fixture child itself so a missing line reports a failure instead of leaving inherited pipes open.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8a0c2316-9385-4f0a-931e-cb9c15c425c5
martinrrm and others added 3 commits November 3, 2026 22:05
Disconnect unused host stdin and verify multiline pipeline input, trailing spaces, and argument forwarding without terminating the native fixture mid-stream.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 8a0c2316-9385-4f0a-931e-cb9c15c425c5
Run every PowerShell host non-interactively with unused stdin disconnected, and report command timings to expose CI process stalls.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8a0c2316-9385-4f0a-931e-cb9c15c425c5
Allow coverage-enabled Windows shim tests to finish their sequential shell invocations. Bound each PowerShell command independently; TAP file-local overrides cannot extend the runner subprocess deadline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8a0c2316-9385-4f0a-931e-cb9c15c425c5

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 globalconfig documentation still lists project-level prefix as a source of globalPrefix, contradicting the updated behavior that ignores it.

1 open issue
3 resolved since last review

Review effort: Max

conf.raw = obj
outer: for (const [key, value] of Object.entries(obj)) {
const k = envReplace(key, this.env)
if (where === 'project' && k === 'prefix') {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants