fix(sandbox): prevent Windows PATH from poisoning docker exec (#13873)

* 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 <noreply@anthropic.com>

* style: add braces around continue to satisfy linter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(test): update assertion to match /bin/sh in buildDockerExecArgs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Alessandro Rodi
2026-03-02 17:17:33 -05:00
committed by GitHub
parent 350ac0d824
commit f257818ea5
3 changed files with 10 additions and 3 deletions

View File

@@ -76,7 +76,7 @@ describe("buildDockerExecArgs", () => {
tty: false,
});
expect(args).toContain("sh");
expect(args).toContain("/bin/sh");
expect(args).toContain("-lc");
});

View File

@@ -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;
}

View File

@@ -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]);
}
}