[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:
@@ -3,7 +3,24 @@ import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, test } from "vitest";
|
||||
|
||||
import { execCommand } from "./spawn.js";
|
||||
import { execCommand, spawnProcess } from "./spawn.js";
|
||||
|
||||
const printEnvScript = `
|
||||
const keys = [
|
||||
"CUSTOM",
|
||||
"ELECTRON_NO_ATTACH_CONSOLE",
|
||||
"ELECTRON_RUN_AS_NODE",
|
||||
"PASEO_DESKTOP_MANAGED",
|
||||
"PASEO_NODE_ENV",
|
||||
"PASEO_SUPERVISED",
|
||||
];
|
||||
const values = Object.fromEntries(keys.map((key) => [key, process.env[key] ?? null]));
|
||||
console.log(JSON.stringify(values));
|
||||
`;
|
||||
|
||||
function parsePrintedEnv(stdout: string): Record<string, string | null> {
|
||||
return JSON.parse(stdout.trim()) as Record<string, string | null>;
|
||||
}
|
||||
|
||||
describe("execCommand", () => {
|
||||
const tempDirs: string[] = [];
|
||||
@@ -51,4 +68,116 @@ describe("execCommand", () => {
|
||||
expect(result.stdout.trim()).toBe(cwd);
|
||||
expect(result.stderr).toBe("");
|
||||
});
|
||||
|
||||
test("treats env as the replacement base and finalizes external command env", async () => {
|
||||
const result = await execCommand(process.execPath, ["-e", printEnvScript], {
|
||||
baseEnv: {
|
||||
ELECTRON_RUN_AS_NODE: "0",
|
||||
CUSTOM: "from-base",
|
||||
PATH: process.env.PATH,
|
||||
PASEO_NODE_ENV: "production",
|
||||
PASEO_SUPERVISED: "1",
|
||||
},
|
||||
env: {
|
||||
CUSTOM: "from-env",
|
||||
ELECTRON_NO_ATTACH_CONSOLE: "1",
|
||||
PASEO_DESKTOP_MANAGED: "1",
|
||||
PASEO_NODE_ENV: "test",
|
||||
},
|
||||
envOverlay: {
|
||||
CUSTOM: "from-overlay",
|
||||
ELECTRON_RUN_AS_NODE: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
expect(parsePrintedEnv(result.stdout)).toEqual({
|
||||
CUSTOM: "from-overlay",
|
||||
ELECTRON_NO_ATTACH_CONSOLE: null,
|
||||
ELECTRON_RUN_AS_NODE: "1",
|
||||
PASEO_DESKTOP_MANAGED: null,
|
||||
PASEO_NODE_ENV: null,
|
||||
PASEO_SUPERVISED: null,
|
||||
});
|
||||
});
|
||||
|
||||
test("does not inherit process.env when env replacement is supplied", async () => {
|
||||
process.env.PASEO_TEST_SHOULD_NOT_LEAK = "leaked";
|
||||
try {
|
||||
const result = await execCommand(
|
||||
process.execPath,
|
||||
[
|
||||
"-e",
|
||||
"console.log(JSON.stringify({ leaked: process.env.PASEO_TEST_SHOULD_NOT_LEAK ?? null }))",
|
||||
],
|
||||
{
|
||||
env: {
|
||||
PATH: process.env.PATH,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(JSON.parse(result.stdout.trim())).toEqual({ leaked: null });
|
||||
} finally {
|
||||
delete process.env.PASEO_TEST_SHOULD_NOT_LEAK;
|
||||
}
|
||||
});
|
||||
|
||||
test("spawnProcess finalizes external command env", async () => {
|
||||
const child = spawnProcess(process.execPath, ["-e", printEnvScript], {
|
||||
baseEnv: {
|
||||
ELECTRON_RUN_AS_NODE: "0",
|
||||
PATH: process.env.PATH,
|
||||
PASEO_NODE_ENV: "production",
|
||||
},
|
||||
envOverlay: {
|
||||
CUSTOM: "spawn-overlay",
|
||||
PASEO_SUPERVISED: "1",
|
||||
},
|
||||
});
|
||||
|
||||
const stdoutChunks: Buffer[] = [];
|
||||
const stderrChunks: Buffer[] = [];
|
||||
child.stdout?.on("data", (chunk: Buffer) => stdoutChunks.push(chunk));
|
||||
child.stderr?.on("data", (chunk: Buffer) => stderrChunks.push(chunk));
|
||||
|
||||
const exitCode = await new Promise<number | null>((resolve, reject) => {
|
||||
child.on("error", reject);
|
||||
child.on("close", resolve);
|
||||
});
|
||||
|
||||
expect(Buffer.concat(stderrChunks).toString()).toBe("");
|
||||
expect(exitCode).toBe(0);
|
||||
expect(parsePrintedEnv(Buffer.concat(stdoutChunks).toString())).toEqual({
|
||||
CUSTOM: "spawn-overlay",
|
||||
ELECTRON_NO_ATTACH_CONSOLE: null,
|
||||
ELECTRON_RUN_AS_NODE: "1",
|
||||
PASEO_DESKTOP_MANAGED: null,
|
||||
PASEO_NODE_ENV: null,
|
||||
PASEO_SUPERVISED: null,
|
||||
});
|
||||
});
|
||||
|
||||
test("internal env mode preserves Paseo-owned launcher env", async () => {
|
||||
const result = await execCommand(process.execPath, ["-e", printEnvScript], {
|
||||
envMode: "internal",
|
||||
baseEnv: {
|
||||
ELECTRON_RUN_AS_NODE: "1",
|
||||
PATH: process.env.PATH,
|
||||
PASEO_NODE_ENV: "production",
|
||||
},
|
||||
envOverlay: {
|
||||
CUSTOM: "internal",
|
||||
PASEO_SUPERVISED: "1",
|
||||
},
|
||||
});
|
||||
|
||||
expect(parsePrintedEnv(result.stdout)).toEqual({
|
||||
CUSTOM: "internal",
|
||||
ELECTRON_NO_ATTACH_CONSOLE: null,
|
||||
ELECTRON_RUN_AS_NODE: "1",
|
||||
PASEO_DESKTOP_MANAGED: null,
|
||||
PASEO_NODE_ENV: "production",
|
||||
PASEO_SUPERVISED: "1",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user