mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 12:22:14 +03:00
* fix(cli): escape codex args and stop aborting on exit in launch-codex `launch-codex` spawns `codex.cmd` with `shell: true` on Windows, so Node joins argv with plain spaces and no escaping (DEP0190). This mangles every Windows invocation, not only the ones with a multi-word user argument, because the injected `-c` provider flags carry quoted TOML values: ["-c","model_provider=omniroute", ..., "model_providers.omniroute.base_url=http://localhost:20128/v1", "fix","the","bug"] cmd.exe strips the TOML quotes (`model_provider=omniroute` no longer parses as a TOML string), splits multi-word arguments, and swallows everything after an unquoted `&`. The same defect was fixed for `launch` in #8837; this ports it to `launch-codex`, which that PR disclosed but left unfixed. - extract the escaping into `bin/cli/utils/winShellArgs.mjs` and reuse it from both launchers instead of keeping a private copy in `launch.mjs` - quote the codex argv (provider flags + profile + pass-through args) on the win32 shell path; argv is untouched off Windows, where no shell is involved - replace `process.exit()` in the command action with `process.exitCode`: on any non-zero child exit it aborted with the libuv `!(handle->flags & UV_HANDLE_CLOSING)` assertion while the inherited stdio handles were closing Test: `tests/unit/cli/launch-codex-windows-spawn-args.test.ts` pins the exact encoding with golden strings (the cmd.exe round-trip is Windows-only and skips on Linux CI, so without goldens CI would guard nothing) and round-trips the real provider flags through a probe `.cmd` shim that forwards `%*`. * docs(changelog): add fragment for #8856 Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
48 lines
1.8 KiB
JavaScript
48 lines
1.8 KiB
JavaScript
/**
|
|
* Argument escaping for child processes spawned with `shell: true` on Windows.
|
|
*
|
|
* The launchers (`omniroute launch`, `omniroute launch-codex`) must go through
|
|
* cmd.exe on win32 because the target binaries are npm `.cmd` shims that Node
|
|
* cannot exec directly (CVE-2024-27980). With `shell: true` Node joins argv with
|
|
* plain spaces and no escaping at all (the DEP0190 warning), so anything with a
|
|
* space, a quote or a cmd metacharacter reaches the child mangled.
|
|
*/
|
|
|
|
/** cmd.exe metacharacters that stay live inside a quoted argument. */
|
|
const WIN_META_CHARS = /([()\][%!^"`<>&|;, *?])/g;
|
|
|
|
/**
|
|
* Escape one argument for a cmd.exe command line built by `shell: true`.
|
|
*
|
|
* Two layers, in order:
|
|
* 1. the CRT argv rules the target binary parses (double the backslashes that
|
|
* precede a quote, escape embedded quotes, wrap in quotes);
|
|
* 2. cmd.exe's metacharacters, caret-escaped — applied TWICE because the
|
|
* target is an npm `.cmd` shim that forwards `%*` to node, so the line is
|
|
* parsed by cmd a second time. Single-escaping truncated any argument at
|
|
* the first `&` or `|`. (Same rule as cross-spawn's doubleEscapeMetaChars.)
|
|
*
|
|
* @param {unknown} arg
|
|
* @returns {string}
|
|
*/
|
|
export function escapeWindowsShellArg(arg) {
|
|
const s = String(arg);
|
|
if (s === "") return '""';
|
|
let out = s.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\*)$/, "$1$1");
|
|
out = `"${out}"`;
|
|
return out.replace(WIN_META_CHARS, "^$1").replace(WIN_META_CHARS, "^$1");
|
|
}
|
|
|
|
/**
|
|
* Escape a whole argv for the `shell: true` path. Off Windows there is no shell,
|
|
* so argv is passed through untouched.
|
|
*
|
|
* @param {string[]} args
|
|
* @param {NodeJS.Platform|string} platform
|
|
* @returns {string[]}
|
|
*/
|
|
export function quoteShellArgs(args, platform) {
|
|
const list = [...(args ?? [])];
|
|
return platform === "win32" ? list.map(escapeWindowsShellArg) : list;
|
|
}
|