mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-04 05:42:02 +00:00
fix(windows): reject unresolved cmd wrappers (#58436)
* fix(windows): reject unresolved cmd wrappers * fix(windows): add wrapper policy coverage * fix(windows): document wrapper fallback migration * fix(windows): drop changelog entry from pr * chore: add changelog for Windows wrapper fail-closed behavior --------- Co-authored-by: Devin Robison <drobison@nvidia.com> Co-authored-by: Devin Robison <drobison00@users.noreply.github.com>
This commit is contained in:
@@ -272,26 +272,21 @@ describe("resolveAcpClientSpawnInvocation", () => {
|
||||
expect(resolved.windowsHide).toBe(true);
|
||||
});
|
||||
|
||||
it("falls back to shell mode for unresolved wrappers on windows", async () => {
|
||||
it("fails closed for unresolved wrappers on windows", async () => {
|
||||
const dir = await createTempDir();
|
||||
const shimPath = path.join(dir, "openclaw.cmd");
|
||||
await writeFile(shimPath, "@ECHO off\r\necho wrapper\r\n", "utf8");
|
||||
|
||||
const resolved = resolveAcpClientSpawnInvocation(
|
||||
{ serverCommand: shimPath, serverArgs: ["acp"] },
|
||||
{
|
||||
platform: "win32",
|
||||
env: { PATH: dir, PATHEXT: ".CMD;.EXE;.BAT" },
|
||||
execPath: "C:\\node\\node.exe",
|
||||
},
|
||||
);
|
||||
|
||||
expect(resolved).toEqual({
|
||||
command: shimPath,
|
||||
args: ["acp"],
|
||||
shell: true,
|
||||
windowsHide: undefined,
|
||||
});
|
||||
expect(() =>
|
||||
resolveAcpClientSpawnInvocation(
|
||||
{ serverCommand: shimPath, serverArgs: ["acp"] },
|
||||
{
|
||||
platform: "win32",
|
||||
env: { PATH: dir, PATHEXT: ".CMD;.EXE;.BAT" },
|
||||
execPath: "C:\\node\\node.exe",
|
||||
},
|
||||
),
|
||||
).toThrow(/without shell execution/);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -260,7 +260,6 @@ export function resolveAcpClientSpawnInvocation(
|
||||
env: runtime.env,
|
||||
execPath: runtime.execPath,
|
||||
packageName: "openclaw",
|
||||
allowShellFallback: true,
|
||||
});
|
||||
const resolved = materializeWindowsSpawnProgram(program, params.serverArgs);
|
||||
return {
|
||||
|
||||
68
src/plugin-sdk/windows-spawn.test.ts
Normal file
68
src/plugin-sdk/windows-spawn.test.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { materializeWindowsSpawnProgram, resolveWindowsSpawnProgram } from "./windows-spawn.js";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
async function createTempDir(): Promise<string> {
|
||||
const dir = await mkdtemp(path.join(tmpdir(), "openclaw-windows-spawn-test-"));
|
||||
tempDirs.push(dir);
|
||||
return dir;
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
while (tempDirs.length > 0) {
|
||||
const dir = tempDirs.pop();
|
||||
if (!dir) {
|
||||
continue;
|
||||
}
|
||||
await rm(dir, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
maxRetries: 8,
|
||||
retryDelay: 8,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe("resolveWindowsSpawnProgram", () => {
|
||||
it("fails closed by default for unresolved windows wrappers", async () => {
|
||||
const dir = await createTempDir();
|
||||
const shimPath = path.join(dir, "wrapper.cmd");
|
||||
await writeFile(shimPath, "@ECHO off\r\necho wrapper\r\n", "utf8");
|
||||
|
||||
expect(() =>
|
||||
resolveWindowsSpawnProgram({
|
||||
command: shimPath,
|
||||
platform: "win32",
|
||||
env: { PATH: dir, PATHEXT: ".CMD;.EXE;.BAT" },
|
||||
execPath: "C:\\node\\node.exe",
|
||||
}),
|
||||
).toThrow(/without shell execution/);
|
||||
});
|
||||
|
||||
it("only returns shell fallback when explicitly opted in", async () => {
|
||||
const dir = await createTempDir();
|
||||
const shimPath = path.join(dir, "wrapper.cmd");
|
||||
await writeFile(shimPath, "@ECHO off\r\necho wrapper\r\n", "utf8");
|
||||
|
||||
const resolved = resolveWindowsSpawnProgram({
|
||||
command: shimPath,
|
||||
platform: "win32",
|
||||
env: { PATH: dir, PATHEXT: ".CMD;.EXE;.BAT" },
|
||||
execPath: "C:\\node\\node.exe",
|
||||
allowShellFallback: true,
|
||||
});
|
||||
const invocation = materializeWindowsSpawnProgram(resolved, ["--cwd", "C:\\safe & calc.exe"]);
|
||||
|
||||
expect(invocation).toEqual({
|
||||
command: shimPath,
|
||||
argv: ["--cwd", "C:\\safe & calc.exe"],
|
||||
resolution: "shell-fallback",
|
||||
shell: true,
|
||||
windowsHide: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -37,6 +37,7 @@ export type ResolveWindowsSpawnProgramParams = {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
execPath?: string;
|
||||
packageName?: string;
|
||||
/** Trusted compatibility escape hatch for callers that intentionally accept shell-mediated wrapper execution. */
|
||||
allowShellFallback?: boolean;
|
||||
};
|
||||
export type ResolveWindowsSpawnProgramCandidateParams = Omit<
|
||||
@@ -265,7 +266,7 @@ export function applyWindowsSpawnProgramPolicy(params: {
|
||||
windowsHide: params.candidate.windowsHide,
|
||||
};
|
||||
}
|
||||
if (params.allowShellFallback !== false) {
|
||||
if (params.allowShellFallback === true) {
|
||||
return {
|
||||
command: params.candidate.command,
|
||||
leadingArgv: [],
|
||||
|
||||
Reference in New Issue
Block a user