mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-14 14:43:16 -06:00
95 lines
3.2 KiB
TypeScript
95 lines
3.2 KiB
TypeScript
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<OptionsType>;
|
|
invocation: ReturnType<typeof resolveSafeChildProcessInvocation>;
|
|
} {
|
|
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<OptionsType>;
|
|
return { child, invocation };
|
|
}
|
|
|
|
/** Spawn through the canonical argv, environment, and Windows safety boundary. */
|
|
export function spawnCommand<OptionsType extends SpawnCommandOptions = SpawnCommandOptions>(
|
|
argv: string[],
|
|
options: OptionsType = {} as OptionsType,
|
|
): ResultPromise<OptionsType> {
|
|
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);
|
|
}
|