Skip to content

fix(core): prevent command injection by removing unsafe cmd.exe fallback on Windows#1448

Open
l3tchupkt wants to merge 1 commit intogoogle:mainfrom
l3tchupkt:fix-win-cmd-rce
Open

fix(core): prevent command injection by removing unsafe cmd.exe fallback on Windows#1448
l3tchupkt wants to merge 1 commit intogoogle:mainfrom
l3tchupkt:fix-win-cmd-rce

Conversation

@l3tchupkt
Copy link
Copy Markdown

Reported-by: LAKSHMIKANTHAN K (letchupkt)
CWE: CWE-78 (Command Injection)

Fixes #issue / suggests an improvement

Summary

This PR fixes a command injection vulnerability on Windows caused by an unsafe fallback to cmd.exe when bash is unavailable.

Previously, if useBash() failed, the error was silently ignored and execution continued with Node’s default shell: true, which resolves to cmd.exe on Windows. However, zx continued using Bash-style quoting ($'...'), which is not supported by cmd.exe, allowing special characters (e.g., &) to break out of argument boundaries and execute arbitrary commands.

This change ensures that zx never falls back to cmd.exe in an unsafe configuration.

Relevant technical choices

  • Added explicit Windows-specific fallback handling when bash is unavailable

  • Introduced safe shell resolution:

    • Prefer pwsh
    • Fallback to powershell.exe
  • If no safe shell is available, throw a Fail error instead of falling back to cmd.exe

  • Updated quoting behavior to use quotePowerShell for PowerShell-based shells

  • Preserved original behavior for non-Windows environments

The fix ensures that command execution always uses a shell compatible with the quoting mechanism, preventing injection vulnerabilities

Impact

Prevents command injection on Windows systems where bash is not installed.

This mitigates:

  • Arbitrary command execution via interpolated variables
  • Silent bypass of zx’s escaping guarantees
  • Execution of unintended commands due to shell mismatch

Usage demo

import {$} from 'zx'
  • Setup Set the latest Node.js LTS version.
  • Build: I’ve run npm build before committing and verified the bundle updates correctly.
  • Tests: I’ve run test and confirmed all tests succeed. Added tests to cover my changes if needed.
  • Docs: I’ve added or updated relevant documentation as needed.
  • Sign Commits have verified signatures and follow conventional commits spec
  • CoC: My changes follow the project’s coding guidelines and Code of Conduct.
  • Review: This PR represents original work and is not solely generated by AI tools.

@google-cla
Copy link
Copy Markdown

google-cla bot commented Mar 30, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

When bash is absent, zx silently fell through to child_process with
shell:true — which on Windows means cmd.exe. cmd.exe does not interpret
bash-style ANSI-C quoting (\$'...''), so every interpolated argument is a
straight injection vector: '& calc &' passes through the quoting layer
unmodified and the metacharacters are executed by cmd.exe.

Fix: make the bash-not-found path Windows-aware.
  - Probe PATH for pwsh then powershell.exe in order of preference.
  - If a PS shell is found, use it with the matching quotePowerShell quoting
    convention and the '; exit \0' postfix.
  - If no safe shell is found at all on win32, throw a Fail with a clear
    diagnostic rather than silently executing in an insecure context.
  - Non-Windows platforms retain the prior (acceptable) behaviour.
  - Explicit shell/prefix/postfix CLI overrides are still honoured after
    auto-detection runs.

Reported-by: LAKSHMIKANTHAN K (letchupkt)
CWE: CWE-78 (OS Command Injection)
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.

1 participant