fix(ci): restore dotenv trust boundary and windows npm exit handling

This commit is contained in:
Vincent Koc 2026-03-31 21:49:28 +09:00
parent 3ceec929df
commit 11590eb6ce
3 changed files with 32 additions and 6 deletions

View File

@ -40,14 +40,23 @@ const BLOCKED_WORKSPACE_DOTENV_KEYS = new Set([
const BLOCKED_WORKSPACE_DOTENV_SUFFIXES = ["_BASE_URL"];
const BLOCKED_WORKSPACE_DOTENV_PREFIXES = ["ANTHROPIC_API_KEY_", "OPENAI_API_KEY_"];
function shouldBlockRuntimeDotEnvKey(key: string): boolean {
function shouldBlockWorkspaceRuntimeDotEnvKey(key: string): boolean {
return isDangerousHostEnvVarName(key) || isDangerousHostEnvOverrideVarName(key);
}
function shouldBlockRuntimeDotEnvKey(key: string): boolean {
// The global ~/.openclaw/.env (or OPENCLAW_STATE_DIR/.env) is a trusted
// operator-controlled runtime surface. Workspace .env is untrusted and gets
// the strict blocklist, but the trusted global fallback is allowed to set
// runtime vars like proxy/base-url/auth values.
void key;
return false;
}
function shouldBlockWorkspaceDotEnvKey(key: string): boolean {
const upper = key.toUpperCase();
return (
shouldBlockRuntimeDotEnvKey(upper) ||
shouldBlockWorkspaceRuntimeDotEnvKey(upper) ||
BLOCKED_WORKSPACE_DOTENV_KEYS.has(upper) ||
BLOCKED_WORKSPACE_DOTENV_PREFIXES.some((prefix) => upper.startsWith(prefix)) ||
BLOCKED_WORKSPACE_DOTENV_SUFFIXES.some((suffix) => upper.endsWith(suffix))

View File

@ -234,13 +234,11 @@ export async function runCommandWithTimeout(
signal: NodeJS.Signals | null;
timedOut: boolean;
noOutputTimedOut: boolean;
killed: boolean;
}): boolean =>
usesWindowsExitCodeShim &&
params.signal == null &&
!params.timedOut &&
!params.noOutputTimedOut &&
!params.killed;
!params.noOutputTimedOut;
const child = spawn(
useCmdWrapper ? (process.env.ComSpec ?? "cmd.exe") : resolvedCommand,
@ -364,7 +362,6 @@ export async function runCommandWithTimeout(
signal: resolvedSignal,
timedOut,
noOutputTimedOut,
killed: child.killed,
})
) {
resolvedCode = 0;

View File

@ -162,6 +162,26 @@ describe("windows command wrapper behavior", () => {
}
});
it("treats shimmed Windows commands without a reported exit code as success even when child.killed is true", async () => {
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
const child = createMockChild({
closeCode: null,
exitCode: null,
});
child.killed = true;
spawnMock.mockImplementation(() => child);
try {
const result = await runCommandWithTimeout(["npm", "--version"], { timeoutMs: 1000 });
expect(result.code).toBe(0);
expect(result.signal).toBeNull();
expect(result.termination).toBe("exit");
} finally {
platformSpy.mockRestore();
}
});
it("uses cmd.exe wrapper with windowsVerbatimArguments in runExec for .cmd shims", async () => {
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
const expectedComSpec = process.env.ComSpec ?? "cmd.exe";