mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 08:31:19 +00:00
- Fix CLI storage path handling: use path.join instead of path.posix.join for proper Windows path construction on local filesystem operations - Fix streamJsonBuilder format: restore correct Claude Code stream-json format with type: 'user' and nested message structure (was incorrectly changed to type: 'user_message' which causes Claude CLI errors) - Add shellEscape utility: create proper shell argument escaping module with documentation and comprehensive tests for cmd.exe and PowerShell - Refactor ChildProcessSpawner: use new shellEscape utility instead of inline escaping logic for better maintainability - Fix type errors: correct ToolType usage (remove invalid 'claude' value) and add explicit Record<string, AgentSshRemoteConfig> typing
122 lines
3.9 KiB
TypeScript
122 lines
3.9 KiB
TypeScript
/**
|
|
* Shell argument escaping utilities for Windows cmd.exe and PowerShell.
|
|
*
|
|
* These functions handle escaping command line arguments to prevent injection
|
|
* and ensure proper argument passing when spawning processes via shell.
|
|
*
|
|
* References:
|
|
* - cmd.exe escaping: https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/cmd
|
|
* - PowerShell escaping: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules
|
|
*
|
|
* For production use, consider using the 'shescape' npm package which provides
|
|
* more comprehensive cross-platform shell escaping with extensive testing.
|
|
*/
|
|
|
|
/**
|
|
* Characters that require quoting in cmd.exe.
|
|
* Based on cmd.exe special characters: https://ss64.com/nt/syntax-esc.html
|
|
*/
|
|
const CMD_SPECIAL_CHARS = /[ &|<>^%!()"\n\r#?*]/;
|
|
|
|
/**
|
|
* Characters that require quoting in PowerShell.
|
|
* Based on PowerShell special characters: https://ss64.com/ps/syntax-esc.html
|
|
*/
|
|
const POWERSHELL_SPECIAL_CHARS = /[ &|<>^%!()"\n\r#?*`$@{}[\]';,]/;
|
|
|
|
/**
|
|
* Escape a single argument for use in cmd.exe.
|
|
*
|
|
* Strategy:
|
|
* 1. If the argument contains special characters or is long, wrap in double quotes
|
|
* 2. Escape existing double quotes by doubling them
|
|
* 3. Escape carets (^) as they're the escape character in cmd.exe
|
|
*
|
|
* @param arg - The argument to escape
|
|
* @returns The escaped argument safe for cmd.exe
|
|
*/
|
|
export function escapeCmdArg(arg: string): string {
|
|
// If no special characters and not too long, return as-is
|
|
if (!CMD_SPECIAL_CHARS.test(arg) && arg.length <= 100) {
|
|
return arg;
|
|
}
|
|
|
|
// Escape double quotes by doubling them, and carets by doubling
|
|
const escaped = arg.replace(/"/g, '""').replace(/\^/g, '^^');
|
|
|
|
// Wrap in double quotes
|
|
return `"${escaped}"`;
|
|
}
|
|
|
|
/**
|
|
* Escape a single argument for use in PowerShell.
|
|
*
|
|
* Strategy:
|
|
* 1. If the argument contains special characters or is long, wrap in single quotes
|
|
* 2. Escape existing single quotes by doubling them (PowerShell's single-quote escape)
|
|
*
|
|
* Single quotes in PowerShell treat the content as a literal string, which is
|
|
* safer than double quotes (which allow variable expansion).
|
|
*
|
|
* @param arg - The argument to escape
|
|
* @returns The escaped argument safe for PowerShell
|
|
*/
|
|
export function escapePowerShellArg(arg: string): string {
|
|
// If no special characters and not too long, return as-is
|
|
if (!POWERSHELL_SPECIAL_CHARS.test(arg) && arg.length <= 100) {
|
|
return arg;
|
|
}
|
|
|
|
// Escape single quotes by doubling them (PowerShell's escape mechanism)
|
|
const escaped = arg.replace(/'/g, "''");
|
|
|
|
// Wrap in single quotes (prevents variable expansion)
|
|
return `'${escaped}'`;
|
|
}
|
|
|
|
/**
|
|
* Escape an array of arguments for use in cmd.exe.
|
|
*
|
|
* @param args - The arguments to escape
|
|
* @returns The escaped arguments safe for cmd.exe
|
|
*/
|
|
export function escapeCmdArgs(args: string[]): string[] {
|
|
return args.map(escapeCmdArg);
|
|
}
|
|
|
|
/**
|
|
* Escape an array of arguments for use in PowerShell.
|
|
*
|
|
* @param args - The arguments to escape
|
|
* @returns The escaped arguments safe for PowerShell
|
|
*/
|
|
export function escapePowerShellArgs(args: string[]): string[] {
|
|
return args.map(escapePowerShellArg);
|
|
}
|
|
|
|
/**
|
|
* Detect if a shell path refers to PowerShell.
|
|
*
|
|
* @param shellPath - The shell path to check
|
|
* @returns True if the shell is PowerShell (either Windows PowerShell or PowerShell Core)
|
|
*/
|
|
export function isPowerShellShell(shellPath: string | undefined): boolean {
|
|
if (!shellPath) return false;
|
|
const lower = shellPath.toLowerCase();
|
|
return lower.includes('powershell') || lower.includes('pwsh');
|
|
}
|
|
|
|
/**
|
|
* Escape arguments based on the target shell.
|
|
*
|
|
* @param args - The arguments to escape
|
|
* @param shell - The shell path or name (optional, defaults to cmd.exe behavior)
|
|
* @returns The escaped arguments
|
|
*/
|
|
export function escapeArgsForShell(args: string[], shell?: string): string[] {
|
|
if (isPowerShellShell(shell)) {
|
|
return escapePowerShellArgs(args);
|
|
}
|
|
return escapeCmdArgs(args);
|
|
}
|