Files
openclaw/extensions/codex/src/app-server/transport-stdio.ts
pashpashpash 78d51dcebe Clear Codex app-server env keys case-insensitively on Windows (#73102)
* fix(codex): clear app-server env case variants

* fix(codex): avoid repeated env clear scans
2026-04-29 05:34:14 +09:00

108 lines
3.2 KiB
TypeScript

import { spawn } from "node:child_process";
import {
materializeWindowsSpawnProgram,
resolveWindowsSpawnProgram,
} from "openclaw/plugin-sdk/windows-spawn";
import type { CodexAppServerStartOptions } from "./config.js";
import type { CodexAppServerTransport } from "./transport.js";
const UNSAFE_ENVIRONMENT_KEYS = new Set(["__proto__", "constructor", "prototype"]);
type CodexAppServerSpawnRuntime = {
platform: NodeJS.Platform;
env: NodeJS.ProcessEnv;
execPath: string;
};
const DEFAULT_SPAWN_RUNTIME: CodexAppServerSpawnRuntime = {
platform: process.platform,
env: process.env,
execPath: process.execPath,
};
export function resolveCodexAppServerSpawnInvocation(
options: CodexAppServerStartOptions,
runtime: CodexAppServerSpawnRuntime = DEFAULT_SPAWN_RUNTIME,
): { command: string; args: string[]; shell?: boolean; windowsHide?: boolean } {
if (options.commandSource === "managed") {
throw new Error("Managed Codex app-server start options must be resolved before spawn.");
}
const program = resolveWindowsSpawnProgram({
command: options.command,
platform: runtime.platform,
env: runtime.env,
execPath: runtime.execPath,
packageName: "@openai/codex",
});
const resolved = materializeWindowsSpawnProgram(program, options.args);
return {
command: resolved.command,
args: resolved.argv,
shell: resolved.shell,
windowsHide: resolved.windowsHide,
};
}
export function resolveCodexAppServerSpawnEnv(
options: Pick<CodexAppServerStartOptions, "env" | "clearEnv">,
baseEnv: NodeJS.ProcessEnv = process.env,
platform: NodeJS.Platform = process.platform,
): NodeJS.ProcessEnv {
const env = Object.create(null) as NodeJS.ProcessEnv;
copySafeEnvironmentEntries(env, baseEnv);
copySafeEnvironmentEntries(env, options.env ?? {});
const keysToClear = normalizedEnvironmentKeys(options.clearEnv ?? []);
if (platform === "win32") {
const lowerCaseKeysToClear = new Set(keysToClear.map((key) => key.toLowerCase()));
for (const candidate of Object.keys(env)) {
if (lowerCaseKeysToClear.has(candidate.toLowerCase())) {
delete env[candidate];
}
}
} else {
for (const key of keysToClear) {
delete env[key];
}
}
return env;
}
function normalizedEnvironmentKeys(rawKeys: readonly string[]): string[] {
const keys: string[] = [];
for (const rawKey of rawKeys) {
const key = rawKey.trim();
if (key.length > 0) {
keys.push(key);
}
}
return keys;
}
function copySafeEnvironmentEntries(
target: NodeJS.ProcessEnv,
source: NodeJS.ProcessEnv | Record<string, string | undefined>,
): void {
for (const [key, value] of Object.entries(source)) {
if (UNSAFE_ENVIRONMENT_KEYS.has(key)) {
continue;
}
target[key] = value;
}
}
export function createStdioTransport(options: CodexAppServerStartOptions): CodexAppServerTransport {
const env = resolveCodexAppServerSpawnEnv(options);
const invocation = resolveCodexAppServerSpawnInvocation(options, {
platform: process.platform,
env,
execPath: process.execPath,
});
return spawn(invocation.command, invocation.args, {
env,
detached: process.platform !== "win32",
shell: invocation.shell,
stdio: ["pipe", "pipe", "pipe"],
windowsHide: invocation.windowsHide,
});
}