[codex] Centralize subprocess env boundaries (#585)
* Centralize subprocess env boundaries * Remove env boundary static audit test * Tighten env boundary process launches * Fix Windows native process launches * Inline provider env pass-throughs
This commit is contained in:
@@ -1,13 +1,27 @@
|
||||
import { execFile, spawn, type ChildProcess, type SpawnOptions } from "node:child_process";
|
||||
import { extname } from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
import { isWindowsCommandScript, quoteWindowsArgument, quoteWindowsCommand } from "./executable.js";
|
||||
import { createExternalCommandProcessEnv, type ProcessEnvRecord } from "../server/paseo-env.js";
|
||||
import {
|
||||
isWindowsCommandScript,
|
||||
quoteWindowsArgument,
|
||||
quoteWindowsCommand,
|
||||
} from "./windows-command.js";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
interface ExecCommandOptions {
|
||||
interface ExternalEnvOptions {
|
||||
baseEnv?: ProcessEnvRecord;
|
||||
envMode?: "external" | "internal";
|
||||
env?: ProcessEnvRecord;
|
||||
envOverlay?: ProcessEnvRecord;
|
||||
}
|
||||
|
||||
export type SpawnProcessOptions = Omit<SpawnOptions, "env"> & ExternalEnvOptions;
|
||||
|
||||
interface ExecCommandOptions extends ExternalEnvOptions {
|
||||
cwd?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
encoding?: BufferEncoding;
|
||||
timeout?: number;
|
||||
maxBuffer?: number;
|
||||
@@ -18,20 +32,48 @@ interface ExecCommandResult {
|
||||
stderr: string;
|
||||
}
|
||||
|
||||
function hasPathSeparator(value: string): boolean {
|
||||
return value.includes("/") || value.includes("\\");
|
||||
}
|
||||
|
||||
function shouldUseWindowsShell(
|
||||
command: string,
|
||||
requestedShell?: boolean | string,
|
||||
): boolean | string {
|
||||
if (isWindowsCommandScript(command)) {
|
||||
return true;
|
||||
}
|
||||
if (requestedShell !== undefined) {
|
||||
return requestedShell;
|
||||
}
|
||||
return process.platform === "win32" && !hasPathSeparator(command) && !extname(command);
|
||||
}
|
||||
|
||||
export function spawnProcess(
|
||||
command: string,
|
||||
args: string[],
|
||||
options?: SpawnOptions,
|
||||
options?: SpawnProcessOptions,
|
||||
): ChildProcess {
|
||||
const { baseEnv, env, envOverlay, ...spawnOptions } = options ?? {};
|
||||
const resolvedBaseEnv = env ?? baseEnv ?? process.env;
|
||||
const isWindows = process.platform === "win32";
|
||||
const shell = isWindowsCommandScript(command) ? true : (options?.shell ?? isWindows);
|
||||
const shell = shouldUseWindowsShell(command, spawnOptions.shell);
|
||||
|
||||
const shouldQuoteForShell = isWindows && shell !== false;
|
||||
const resolvedCommand = shouldQuoteForShell ? quoteWindowsCommand(command) : command;
|
||||
const resolvedArgs = shouldQuoteForShell ? args.map(quoteWindowsArgument) : args;
|
||||
const childEnv =
|
||||
options?.envMode === "internal"
|
||||
? ({ ...resolvedBaseEnv, ...envOverlay } as NodeJS.ProcessEnv)
|
||||
: createExternalCommandProcessEnv(
|
||||
command,
|
||||
resolvedBaseEnv,
|
||||
...(envOverlay ? [envOverlay] : []),
|
||||
);
|
||||
|
||||
return spawn(resolvedCommand, resolvedArgs, {
|
||||
...options,
|
||||
...spawnOptions,
|
||||
env: childEnv,
|
||||
shell,
|
||||
windowsHide: true,
|
||||
});
|
||||
@@ -42,15 +84,25 @@ export async function execCommand(
|
||||
args: string[],
|
||||
options?: ExecCommandOptions,
|
||||
): Promise<ExecCommandResult> {
|
||||
const { baseEnv, env, envOverlay } = options ?? {};
|
||||
const resolvedBaseEnv = env ?? baseEnv ?? process.env;
|
||||
const isWindows = process.platform === "win32";
|
||||
const shell = isWindowsCommandScript(command) ? true : isWindows;
|
||||
const shell = shouldUseWindowsShell(command);
|
||||
const shouldQuoteForShell = isWindows && shell !== false;
|
||||
const resolvedCommand = shouldQuoteForShell ? quoteWindowsCommand(command) : command;
|
||||
const resolvedArgs = shouldQuoteForShell ? args.map(quoteWindowsArgument) : args;
|
||||
const childEnv =
|
||||
options?.envMode === "internal"
|
||||
? ({ ...resolvedBaseEnv, ...envOverlay } as NodeJS.ProcessEnv)
|
||||
: createExternalCommandProcessEnv(
|
||||
command,
|
||||
resolvedBaseEnv,
|
||||
...(envOverlay ? [envOverlay] : []),
|
||||
);
|
||||
|
||||
return execFileAsync(resolvedCommand, resolvedArgs, {
|
||||
cwd: options?.cwd,
|
||||
env: options?.env,
|
||||
env: childEnv,
|
||||
encoding: options?.encoding ?? "utf8",
|
||||
timeout: options?.timeout,
|
||||
maxBuffer: options?.maxBuffer,
|
||||
|
||||
Reference in New Issue
Block a user