Skip to main content

Module cmdline

Module cmdline 

Source
Expand description

Building a cmd.exe command line for a batch shim.

§Why this module has to exist at all

npm, pnpm, npx, yarn and tsc are .cmd files on Windows, not executables. Two separate things then go wrong:

  1. std::process::Command’s program resolution only ever appends .exe, so Command::new("npm") does not find npm.cmd and fails with “not found”. crate::launch resolves through which, which honours PATHEXT.
  2. A batch file is not executed by the loader; cmd.exe interprets it. That means the arguments are parsed twice – once when cmd.exe reads the command line we hand it, and again inside the script when %* is substituted into a line and that line is re-parsed. Getting this wrong is CVE-2024-24576: an argument containing & becomes a second command.

§The algorithm

This mirrors the escaping the Rust standard library adopted for the CVE-2024-24576 fix, which is the only construction that is known to survive both parses. Reimplementing it here rather than deferring to std is what lets prk run -- npm test work: std applies it only when it resolved the program to a .bat/.cmd, and it never resolves npm to npm.cmd at all.

The pieces, each of which is load-bearing:

PieceDefeats
Wrap the whole command in one outer quote paircmd.exe’s argument splitting
/sThe conditional “should I strip the outer quotes” rule, which is genuinely hard to predict
/dAn AutoRun registry value running before the command
/v:OFF!DELAYED! expansion
/e:ONNeeded for the % construction below to evaluate
Quote any argument that is not purely alphanumeric& | < > ( ) ^ and whitespace splitting
Double an inner " rather than backslash-escaping itA \" would end the quoted region as far as cmd.exe is concerned, exposing everything after it
Replace % with %%cd:~,%%PATH% expanding to its value

The % construction deserves a sentence. %cd:~,% is a substring of the built-in cd variable with an empty start and end index, so it expands to nothing. Splicing that no-op in front of every % leaves the text unchanged but leaves cmd.exe with no %NAME% pair to match, so nothing expands.

§What cannot be escaped

\r and \n terminate a cmd.exe command line; there is no encoding that carries them through. They are rejected rather than silently dropped – silently dropping half an argument is how a prk run invocation quietly does something other than what it was asked to.

§Portability of this module

Everything here operates on UTF-16 code units and is compiled on every platform, so the adversarial-argument tests run on Linux and macOS CI too. Only crate::launch restricts its use to Windows.

Structs§

Utf16Display
A Vec<u16> rendered for a test assertion or a diagnostic.

Enums§

CmdLineError
An argument that cannot be carried through cmd.exe at all.

Constants§

CMD_SWITCHES
Switches passed to cmd.exe ahead of the command itself.

Functions§

append_argument
Appends one escaped argument to a cmd.exe command line.
batch_command_line
Builds the argument string for cmd.exe, running script with args.