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.
Assisted-by: GitHub Copilot CLI:claude-opus-5
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Public API changes for crate: brush-coreAdded itemsPerformance Benchmark Report
Code Coverage Report: Only Changed Files listed
Minimum allowed coverage is Test Summary: bash-completion test suite
|
reubeno
left a comment
There was a problem hiding this comment.
Thanks for highlighting the issue that you're seeing in this scenario. May main concern is whether this is addressing the problem at the right layer of the stack, and whether this kind of logic could cause problems for other called programs.
Let me do a bit more research, think about it a bit more, and respond back with some other suggestions for consideration.
| /// | ||
| /// That second round is not hypothetical: programs built on the MSYS2/Cygwin | ||
| /// runtime (for example, the tools shipped with Git for Windows) re-expand the | ||
| /// arguments they receive from a native parent, applying glob, brace, and tilde |
There was a problem hiding this comment.
This makes it sound like the issue is not actually a brush-specific issue, but a challenging assumption made by msys2/cygwin tools. Perhaps I'm missing something, but wouldn't this be a problem for any Rust application that might use std::process::Command to invoke a program on Windows that might behave like this?
Do you know if there are precedents for how any other such apps have handled this?
| /// # Arguments | ||
| /// | ||
| /// * `program` - The program the command will execute. | ||
| fn uses_cmd_exe_parsing(program: &OsStr) -> bool { |
There was a problem hiding this comment.
This kind of callee-specific logic, to me, begs the question of how many other applications may similarly have challenges and what risk there is in encoding such specific logic in our general execution paths. I understand there are real limitations that this PR is trying to mitigate but I worry that this is a slippery slope toward accumulated heuristics for how to interact with specific (Windows) invoked programs, and that's something that I would expect a general-purpose shell avoids.
|
@cataggar -- some additional things I found or thought about while doing a bit more research:
|
Overview
Preserve shell-expanded arguments when launching external commands on Windows, including programs built on the MSYS2 or Cygwin runtime.
This change was made by GitHub Copilot at my direction.
Details
Windows passes a child process one command-line string rather than an
argvarray. Rust's standard library quotes arguments when required for native Windows parsing, but otherwise leaves them unquoted. MSYS2 and Cygwin programs can then apply their own glob, brace, tilde, and quote processing to those arguments, corrupting values that the shell has already expanded.Examples include:
This change:
sys::commands::set_args;Command::argsbehavior;CommandLineToArgvWand Microsoft C runtime rules;cmd.exe,.bat, and.cmdfiles because they require different escaping and injection protections; andThe Windows module includes focused tests for ordinary arguments, MSYS metacharacters, embedded quotes, trailing backslashes,
cmd.exedetection, batch-file behavior, and an optional end-to-end check using Git for Windows' MSYS2printf.exe.Testing
cargo test --package brush-corecargo check --package brush-core --all-features --all-targets --target x86_64-pc-windows-gnucargo clippy --package brush-core --all-features --all-targets --target x86_64-pc-windows-gnucargo xtask check fmt