Skip to content

argv to a .cmd shim is re-parsed by cmd.exe — untrusted text becomes commands

args quoting · case

bothsilentagentscriptfirst-partyrepro: verifiedcmd-reparse
Affectsnode, cmd, windows
Fails assilent
Mechanismcmd reparse
Safe fixstdin payload

A tool passes user text (a prompt, a title, a model name) as an argument to a CLI installed as a .cmd shim on Windows. Text containing an ampersand, a pipe, or even a bare newline executes EXTRA COMMANDS at the tool’s privileges.

// codex is codex.cmd on Windows; the .cmd launch goes through cmd.exe,
// which re-parses the whole line:
spawn("codex.cmd", ["exec", userText]);
// userText = "hello & calc.exe" → calc runs.
// Newlines work too: cmd treats CR/LF as command boundaries.

Launching a .cmd/.bat file ALWAYS involves cmd.exe, and cmd re-parses the assembled command line — argv boundaries do not survive. Any untrusted byte sequence containing cmd metacharacters (& | < > ^ % and CR/LF) escapes the argument and becomes command syntax. This is the same mechanism Node hardened with CVE-2024-27980 (EINVAL on naive .cmd spawn), but wrappers that route via ComSpec re-open it.

  • Put untrusted payloads on STDIN, never argv (the referenced fix moves the prompt to stdin “-”).
  • Before any ComSpec fallback, reject or strip command-separator bytes including CR/LF (the second referenced commit adds newlines to the separator set).