import path from "node:path"; import process from "node:process"; import { execa, type Options as ExecaOptions, type ResultPromise } from "execa"; import { markOpenClawExecEnv } from "../infra/openclaw-exec-env.js"; import { mergeProcessEnv } from "../infra/process-env.js"; import { resolveSafeChildProcessInvocation } from "./windows-command.js"; export const COMMAND_PROCESS_TREE_KILL_GRACE_MS = 300; export function shouldSpawnWithShell(params: { resolvedCommand: string; platform: NodeJS.Platform; }): boolean { // SECURITY: never enable `shell` for argv-based execution. // `shell` routes through cmd.exe on Windows, which turns untrusted argv values // (like chat prompts passed as CLI args) into command-injection primitives. // If you need a shell, use an explicit shell-wrapper argv (e.g. `cmd.exe /c ...`) // and validate/escape at the call site. void params; return false; } type SpawnCommandOptions = ExecaOptions & { baseEnv?: NodeJS.ProcessEnv; }; export function spawnCommandWithInvocation< OptionsType extends SpawnCommandOptions = SpawnCommandOptions, >( argv: string[], options: OptionsType = {} as OptionsType, ): { child: ResultPromise; invocation: ReturnType; } { const { baseEnv, env, windowsVerbatimArguments, ...execaOptions } = options; const commandEnv = resolveCommandEnv({ argv, baseEnv, env }); const invocation = resolveSafeChildProcessInvocation({ argv, cwd: execaOptions.cwd, env: commandEnv, windowsVerbatimArguments, }); const child = execa(invocation.command, invocation.args, { ...execaOptions, env: commandEnv, extendEnv: false, shell: false, windowsHide: invocation.windowsHide, windowsVerbatimArguments: invocation.windowsVerbatimArguments, } as ExecaOptions) as unknown as ResultPromise; return { child, invocation }; } /** Spawn through the canonical argv, environment, and Windows safety boundary. */ export function spawnCommand( argv: string[], options: OptionsType = {} as OptionsType, ): ResultPromise { return spawnCommandWithInvocation(argv, options).child; } export function resolveCommandEnv(params: { argv: string[]; env?: NodeJS.ProcessEnv; baseEnv?: NodeJS.ProcessEnv; platform?: NodeJS.Platform; }): NodeJS.ProcessEnv { const baseEnv = params.baseEnv ?? process.env; const platform = params.platform ?? process.platform; const argv = params.argv; const shouldSuppressNpmFund = (() => { const cmd = path.basename(argv[0] ?? ""); if (cmd === "npm" || cmd === "npm.cmd" || cmd === "npm.exe") { return true; } if (cmd === "node" || cmd === "node.exe") { const script = argv[1] ?? ""; return script.includes("npm-cli.js"); } return false; })(); const resolvedEnv = mergeProcessEnv([baseEnv, params.env], platform); if (shouldSuppressNpmFund) { if (resolvedEnv.NPM_CONFIG_FUND == null) { resolvedEnv.NPM_CONFIG_FUND = "false"; } if (resolvedEnv.npm_config_fund == null) { resolvedEnv.npm_config_fund = "false"; } } return markOpenClawExecEnv(resolvedEnv); }