Files
orca/src/shared/windows-batch-spawn.ts
T
NeilandOrca 9d473c8c5b fix(windows): stop rejecting .cmd spawns under Program Files (x86) (#11686)
The move of hasUnsafeWindowsBatchSyntax into src/shared/windows-batch-spawn.ts
silently added `(` and `)` to the cmd.exe denylist, so every .cmd shim or
argument path containing parentheses became unspawnable across nine call sites.
Parentheses only group commands and cannot chain one without a separator the
guard already rejects, so they are dropped again.

The rejected character set is now the single source for the user-facing error
strings, and `orca account add` translates the sentinel into a real message.

Co-authored-by: Orca <help@stably.ai>
2026-07-31 01:01:35 -07:00

58 lines
2.1 KiB
TypeScript

import { win32 } from 'node:path'
/** Full path to cmd.exe for GUI and service-launched processes. */
export function getCmdExePath(): string {
return (
process.env.ComSpec ||
win32.join(process.env.SystemRoot ?? 'C:\\Windows', 'System32', 'cmd.exe')
)
}
export function isWindowsBatchScript(commandPath: string): boolean {
return process.platform === 'win32' && /\.(cmd|bat)$/i.test(commandPath)
}
export const WINDOWS_BATCH_UNSAFE_ARGUMENTS_ERROR = 'UNSAFE_WINDOWS_BATCH_ARGUMENTS'
export class UnsafeWindowsBatchArgumentsError extends Error {
constructor() {
super(WINDOWS_BATCH_UNSAFE_ARGUMENTS_ERROR)
this.name = 'UnsafeWindowsBatchArgumentsError'
}
}
// Why: cmd.exe re-parses the command line, and these are the characters that can
// start a new command or expand a variable out of an otherwise inert argument.
// `(`/`)` are deliberately absent: they only group commands, and grouping cannot
// chain anything without one of the separators below, so rejecting them merely
// broke every `C:\Program Files (x86)\...` shim and paren-bearing worktree path.
const WINDOWS_BATCH_UNSAFE_CHARACTERS = ['&', '|', '<', '>', '^', '"', '%', '!'] as const
/** The rejected characters, spelled for error messages so they cannot drift from the guard. */
export const WINDOWS_BATCH_UNSAFE_CHARACTERS_LABEL = WINDOWS_BATCH_UNSAFE_CHARACTERS.join(' ')
const UNSAFE_WINDOWS_BATCH_SYNTAX = new RegExp(
`[${WINDOWS_BATCH_UNSAFE_CHARACTERS.map((character) => character.replace(/[\\^\]-]/, '\\$&')).join('')}\\r\\n]`
)
function hasUnsafeWindowsBatchSyntax(value: string): boolean {
return UNSAFE_WINDOWS_BATCH_SYNTAX.test(value)
}
export function getSpawnArgsForWindows(
command: string,
args: string[]
): { spawnCmd: string; spawnArgs: string[] } {
if (isWindowsBatchScript(command)) {
for (const value of [command, ...args]) {
if (hasUnsafeWindowsBatchSyntax(value)) {
throw new UnsafeWindowsBatchArgumentsError()
}
}
// Why: separate argv entries let Node quote spaces without breaking cmd.
return { spawnCmd: getCmdExePath(), spawnArgs: ['/d', '/c', command, ...args] }
}
return { spawnCmd: command, spawnArgs: args }
}