mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
fix(sandbox): sanitize Docker env before marking OPENCLAW_CLI (#42256)
* Sandbox: sanitize Docker env before exec marker injection * Sandbox: add regression test for Docker exec marker env * Sandbox: disable Windows shell fallback for Docker * Sandbox: cover Windows Docker wrapper rejection * Sandbox: test strict env sanitization through Docker args
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user