diff --git a/src/agents/sandbox-create-args.test.ts b/src/agents/sandbox-create-args.test.ts index 0d9621ad9e1..60b6241f58a 100644 --- a/src/agents/sandbox-create-args.test.ts +++ b/src/agents/sandbox-create-args.test.ts @@ -137,6 +137,33 @@ describe("buildSandboxCreateArgs", () => { ); }); + it("preserves the OpenClaw exec marker when strict env sanitization is enabled", () => { + const cfg = createSandboxConfig({ + env: { + NODE_ENV: "test", + }, + }); + + const args = buildSandboxCreateArgs({ + name: "openclaw-sbx-marker", + cfg, + scopeKey: "main", + createdAtMs: 1700000000000, + envSanitizationOptions: { + strictMode: true, + }, + }); + + expect(args).toEqual( + expect.arrayContaining([ + "--env", + "NODE_ENV=test", + "--env", + `OPENCLAW_CLI=${OPENCLAW_CLI_ENV_VALUE}`, + ]), + ); + }); + it("emits -v flags for safe custom binds", () => { const cfg: SandboxDockerConfig = { image: "openclaw-sandbox:bookworm-slim", diff --git a/src/agents/sandbox/docker.ts b/src/agents/sandbox/docker.ts index 68c95e343ea..aefceb08495 100644 --- a/src/agents/sandbox/docker.ts +++ b/src/agents/sandbox/docker.ts @@ -5,6 +5,7 @@ import { resolveWindowsSpawnProgram, } from "../../plugin-sdk/windows-spawn.js"; import { sanitizeEnvVars } from "./sanitize-env-vars.js"; +import type { EnvSanitizationOptions } from "./sanitize-env-vars.js"; type ExecDockerRawOptions = { allowFailure?: boolean; @@ -52,7 +53,7 @@ export function resolveDockerSpawnInvocation( env: runtime.env, execPath: runtime.execPath, packageName: "docker", - allowShellFallback: true, + allowShellFallback: false, }); const resolved = materializeWindowsSpawnProgram(program, args); return { @@ -325,6 +326,7 @@ export function buildSandboxCreateArgs(params: { allowSourcesOutsideAllowedRoots?: boolean; allowReservedContainerTargets?: boolean; allowContainerNamespaceJoin?: boolean; + envSanitizationOptions?: EnvSanitizationOptions; }) { // Runtime security validation: blocks dangerous bind mounts, network modes, and profiles. validateSandboxSecurity({ @@ -366,14 +368,14 @@ export function buildSandboxCreateArgs(params: { if (params.cfg.user) { args.push("--user", params.cfg.user); } - const envSanitization = sanitizeEnvVars(markOpenClawExecEnv(params.cfg.env ?? {})); + const envSanitization = sanitizeEnvVars(params.cfg.env ?? {}, params.envSanitizationOptions); if (envSanitization.blocked.length > 0) { log.warn(`Blocked sensitive environment variables: ${envSanitization.blocked.join(", ")}`); } if (envSanitization.warnings.length > 0) { log.warn(`Suspicious environment variables: ${envSanitization.warnings.join(", ")}`); } - for (const [key, value] of Object.entries(envSanitization.allowed)) { + for (const [key, value] of Object.entries(markOpenClawExecEnv(envSanitization.allowed))) { args.push("--env", `${key}=${value}`); } for (const cap of params.cfg.capDrop) { diff --git a/src/agents/sandbox/docker.windows.test.ts b/src/agents/sandbox/docker.windows.test.ts index 3dd294e8360..7abebad98ab 100644 --- a/src/agents/sandbox/docker.windows.test.ts +++ b/src/agents/sandbox/docker.windows.test.ts @@ -47,22 +47,20 @@ describe("resolveDockerSpawnInvocation", () => { }); }); - it("falls back to shell mode when only unresolved docker.cmd wrapper exists", async () => { + it("rejects unresolved docker.cmd wrappers instead of shelling out", async () => { const dir = await createTempDir(); const cmdPath = path.join(dir, "docker.cmd"); await mkdir(path.dirname(cmdPath), { recursive: true }); await writeFile(cmdPath, "@ECHO off\r\necho docker\r\n", "utf8"); - const resolved = resolveDockerSpawnInvocation(["ps"], { - platform: "win32", - env: { PATH: dir, PATHEXT: ".CMD;.EXE;.BAT" }, - execPath: "C:\\node\\node.exe", - }); - expect(path.normalize(resolved.command).toLowerCase()).toBe( - path.normalize(cmdPath).toLowerCase(), + expect(() => + resolveDockerSpawnInvocation(["ps"], { + platform: "win32", + env: { PATH: dir, PATHEXT: ".CMD;.EXE;.BAT" }, + execPath: "C:\\node\\node.exe", + }), + ).toThrow( + /wrapper resolved, but no executable\/Node entrypoint could be resolved without shell execution\./i, ); - expect(resolved.args).toEqual(["ps"]); - expect(resolved.shell).toBe(true); - expect(resolved.windowsHide).toBeUndefined(); }); });