fix(core): quote arguments passed to external commands on Windows - #17
Merged
Merged
Conversation
Windows has no argv array: a child process receives a single command-line
string and parses it itself. The standard library only quotes an argument
when it is empty or contains whitespace or a quote, which leaves every
other argument exposed to a second round of interpretation in the child.
Programs built on the MSYS2/Cygwin runtime -- including the tools shipped
with Git for Windows -- re-expand the arguments they receive from a native
parent, applying glob, brace, and tilde expansion along with quote removal.
Words the shell had already finished expanding were silently corrupted:
grep -oE '[0-9]{3}' -> child saw [0-9]3
grep -oE 'MAC\(x' -> child saw MAC(x
find . -name '*.txt' -> child saw every file in the directory
printf '%s' '~' -> child saw the home directory
Explicitly quote every argument using the rules understood by
CommandLineToArgvW so the child recovers exactly what the shell expanded.
Native programs are unaffected, since they strip the quotes while parsing.
Arguments bound for cmd.exe are left to the standard library to encode:
cmd.exe rejects quotes around its own options, and the standard library
applies extra escaping when spawning a batch file that a raw command line
would bypass. Neither is an MSYS2/Cygwin program, so no protection is lost.
Fixes #16
Assisted-by: GitHub Copilot CLI:claude-opus-5
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
cataggar
force-pushed
the
fix/windows-external-command-arg-quoting
branch
from
August 20, 2026 18:13
51d1783 to
7f9c1c8
Compare
cataggar
added a commit
that referenced
this pull request
Aug 22, 2026
Windows has no argv array: a child process receives a single command-line
string and parses it itself. The standard library only quotes an argument
when it is empty or contains whitespace or a quote, which leaves every
other argument exposed to a second round of interpretation in the child.
Programs built on the MSYS2/Cygwin runtime -- including the tools shipped
with Git for Windows -- re-expand the arguments they receive from a native
parent, applying glob, brace, and tilde expansion along with quote removal.
Words the shell had already finished expanding were silently corrupted:
grep -oE '[0-9]{3}' -> child saw [0-9]3
grep -oE 'MAC\(x' -> child saw MAC(x
find . -name '*.txt' -> child saw every file in the directory
printf '%s' '~' -> child saw the home directory
Explicitly quote every argument using the rules understood by
CommandLineToArgvW so the child recovers exactly what the shell expanded.
Native programs are unaffected, since they strip the quotes while parsing.
Arguments bound for cmd.exe are left to the standard library to encode:
cmd.exe rejects quotes around its own options, and the standard library
applies extra escaping when spawning a batch file that a raw command line
would bypass. Neither is an MSYS2/Cygwin program, so no protection is lost.
Fixes #16
Assisted-by: GitHub Copilot CLI:claude-opus-5
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #16.
Root cause
Windows has no
argvarray: a child process receives a single command-line string and parses it itself.std::process::Commandonly wraps an argument in double quotes when it is empty or contains whitespace or a quote, so every other argument is emitted bare.Programs built on the MSYS2/Cygwin runtime — including all the tools shipped with Git for Windows — re-parse that raw command line and run Cygwin's
globifyon it, applying glob, brace, and tilde expansion plus quote removal. Words that brush had already finished expanding got expanded a second time by the child.Git-for-Windows
bashis immune because it is itself an MSYS program and handsargvto MSYS children over the Cygwin IPC channel, so no re-parse happens.Impact is wider than the issue reported
Measured by passing each argument through to Git's
printf.exe:[0-9]{3}[0-9]3[0-9]{3}MAC\([A-Fa-f0-9]{12}MAC([A-Fa-f0-9]12x{a,b}yxay,xby(two arguments)**~/c/Users/<user>~a'baba'bThe
*and~rows are not in the original report but are arguably worse:find . -name '*.txt'andgrep -r x *were silently operating on the wrong input.Fix
Quote every argument explicitly using the rules understood by
CommandLineToArgvWand the Microsoft C runtime, viaCommandExt::raw_arg. Native programs are unaffected because they strip the quotes while parsing. (CommandExt::force_quoteswould be the natural API, but it is still unstable — rust-lang/rust#82227 — and the MSRV here is 1.88 stable.)Argument encoding is now routed through a new
sys::commands::set_args, which is a straight passthrough toCommand::argson unix and wasm.Two carve-outs, both verified necessary
Arguments bound for
cmd.exeare left to the standard library to encode:cmd.exerejects quotes around its own options. A command line ofcmd.exe "/c" "ver"fails with'"ver' is not recognized as an internal or external command.stdruns.bat/.cmdthroughcmd.exeand neutralises metacharacters such as%(the CVE-2024-24576 mitigation). Encoding a raw command line bypasses that: a literal%CD%argument was observed being expanded to the working directory.Neither
cmd.exenor a batch file is an MSYS2/Cygwin program, so no protection is lost by deferring to the standard library for them.Testing
New unit tests in
brush-core/src/sys/windows/commands.rscover the quoting rules (metacharacters, embedded quotes, trailing backslashes, empty arguments) and the carve-out detection, plus three end-to-end tests:cmd.exe /c "echo hello world"still runs.batstill receives%CD%literallyCI runs the suite on
windows-2025, so these execute there.Verified manually against the issue's repro:
Also checked:
cargo xtask test integration(401 passed),cargo clippy --workspace --all-features --all-targets,cargo fmt --check, andcargo checkagainstx86_64-unknown-linux-gnuandwasm32-unknown-unknownto confirm the non-Windows paths are unaffected.Assisted-by: GitHub Copilot CLI:claude-opus-5