From f257818ea525335625cec8a6df7052daffff49f0 Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Mon, 2 Mar 2026 17:17:33 -0500 Subject: [PATCH] fix(sandbox): prevent Windows PATH from poisoning docker exec (#13873) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(sandbox): prevent Windows PATH from poisoning docker exec shell lookup On Windows hosts, `buildDockerExecArgs` passes the host PATH env var (containing Windows paths like `C:\Windows\System32`) to `docker exec -e PATH=...`. Docker uses this PATH to resolve the executable argument (`sh`), which fails because Windows paths don't exist in the Linux container — producing `exec: "sh": executable file not found in $PATH`. Two changes: - Skip PATH in the `-e` env loop (it's already handled separately via OPENCLAW_PREPEND_PATH + shell export) - Use absolute `/bin/sh` instead of bare `sh` to eliminate PATH dependency entirely Co-Authored-By: Claude Opus 4.6 * style: add braces around continue to satisfy linter Co-Authored-By: Claude Opus 4.6 * fix(test): update assertion to match /bin/sh in buildDockerExecArgs Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- src/agents/bash-tools.build-docker-exec-args.test.ts | 2 +- src/agents/bash-tools.shared.ts | 9 ++++++++- src/agents/sandbox/docker.ts | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/agents/bash-tools.build-docker-exec-args.test.ts b/src/agents/bash-tools.build-docker-exec-args.test.ts index b759a51b5..6cdc981f6 100644 --- a/src/agents/bash-tools.build-docker-exec-args.test.ts +++ b/src/agents/bash-tools.build-docker-exec-args.test.ts @@ -76,7 +76,7 @@ describe("buildDockerExecArgs", () => { tty: false, }); - expect(args).toContain("sh"); + expect(args).toContain("/bin/sh"); expect(args).toContain("-lc"); }); diff --git a/src/agents/bash-tools.shared.ts b/src/agents/bash-tools.shared.ts index 2a9bc8216..3cfb92655 100644 --- a/src/agents/bash-tools.shared.ts +++ b/src/agents/bash-tools.shared.ts @@ -61,6 +61,12 @@ export function buildDockerExecArgs(params: { args.push("-w", params.workdir); } for (const [key, value] of Object.entries(params.env)) { + // Skip PATH — passing a host PATH (e.g. Windows paths) via -e poisons + // Docker's executable lookup, causing "sh: not found" on Windows hosts. + // PATH is handled separately via OPENCLAW_PREPEND_PATH below. + if (key === "PATH") { + continue; + } args.push("-e", `${key}=${value}`); } const hasCustomPath = typeof params.env.PATH === "string" && params.env.PATH.length > 0; @@ -75,7 +81,8 @@ export function buildDockerExecArgs(params: { const pathExport = hasCustomPath ? 'export PATH="${OPENCLAW_PREPEND_PATH}:$PATH"; unset OPENCLAW_PREPEND_PATH; ' : ""; - args.push(params.containerName, "sh", "-lc", `${pathExport}${params.command}`); + // Use absolute path for sh to avoid dependency on PATH resolution during exec. + args.push(params.containerName, "/bin/sh", "-lc", `${pathExport}${params.command}`); return args; } diff --git a/src/agents/sandbox/docker.ts b/src/agents/sandbox/docker.ts index df1ba3ef3..e041c2d5c 100644 --- a/src/agents/sandbox/docker.ts +++ b/src/agents/sandbox/docker.ts @@ -469,7 +469,7 @@ async function createSandboxContainer(params: { await execDocker(["start", name]); if (cfg.setupCommand?.trim()) { - await execDocker(["exec", "-i", name, "sh", "-lc", cfg.setupCommand]); + await execDocker(["exec", "-i", name, "/bin/sh", "-lc", cfg.setupCommand]); } }