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", () => {
|
it("emits -v flags for safe custom binds", () => {
|
||||||
const cfg: SandboxDockerConfig = {
|
const cfg: SandboxDockerConfig = {
|
||||||
image: "openclaw-sandbox:bookworm-slim",
|
image: "openclaw-sandbox:bookworm-slim",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
resolveWindowsSpawnProgram,
|
resolveWindowsSpawnProgram,
|
||||||
} from "../../plugin-sdk/windows-spawn.js";
|
} from "../../plugin-sdk/windows-spawn.js";
|
||||||
import { sanitizeEnvVars } from "./sanitize-env-vars.js";
|
import { sanitizeEnvVars } from "./sanitize-env-vars.js";
|
||||||
|
import type { EnvSanitizationOptions } from "./sanitize-env-vars.js";
|
||||||
|
|
||||||
type ExecDockerRawOptions = {
|
type ExecDockerRawOptions = {
|
||||||
allowFailure?: boolean;
|
allowFailure?: boolean;
|
||||||
@@ -52,7 +53,7 @@ export function resolveDockerSpawnInvocation(
|
|||||||
env: runtime.env,
|
env: runtime.env,
|
||||||
execPath: runtime.execPath,
|
execPath: runtime.execPath,
|
||||||
packageName: "docker",
|
packageName: "docker",
|
||||||
allowShellFallback: true,
|
allowShellFallback: false,
|
||||||
});
|
});
|
||||||
const resolved = materializeWindowsSpawnProgram(program, args);
|
const resolved = materializeWindowsSpawnProgram(program, args);
|
||||||
return {
|
return {
|
||||||
@@ -325,6 +326,7 @@ export function buildSandboxCreateArgs(params: {
|
|||||||
allowSourcesOutsideAllowedRoots?: boolean;
|
allowSourcesOutsideAllowedRoots?: boolean;
|
||||||
allowReservedContainerTargets?: boolean;
|
allowReservedContainerTargets?: boolean;
|
||||||
allowContainerNamespaceJoin?: boolean;
|
allowContainerNamespaceJoin?: boolean;
|
||||||
|
envSanitizationOptions?: EnvSanitizationOptions;
|
||||||
}) {
|
}) {
|
||||||
// Runtime security validation: blocks dangerous bind mounts, network modes, and profiles.
|
// Runtime security validation: blocks dangerous bind mounts, network modes, and profiles.
|
||||||
validateSandboxSecurity({
|
validateSandboxSecurity({
|
||||||
@@ -366,14 +368,14 @@ export function buildSandboxCreateArgs(params: {
|
|||||||
if (params.cfg.user) {
|
if (params.cfg.user) {
|
||||||
args.push("--user", 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) {
|
if (envSanitization.blocked.length > 0) {
|
||||||
log.warn(`Blocked sensitive environment variables: ${envSanitization.blocked.join(", ")}`);
|
log.warn(`Blocked sensitive environment variables: ${envSanitization.blocked.join(", ")}`);
|
||||||
}
|
}
|
||||||
if (envSanitization.warnings.length > 0) {
|
if (envSanitization.warnings.length > 0) {
|
||||||
log.warn(`Suspicious environment variables: ${envSanitization.warnings.join(", ")}`);
|
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}`);
|
args.push("--env", `${key}=${value}`);
|
||||||
}
|
}
|
||||||
for (const cap of params.cfg.capDrop) {
|
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 dir = await createTempDir();
|
||||||
const cmdPath = path.join(dir, "docker.cmd");
|
const cmdPath = path.join(dir, "docker.cmd");
|
||||||
await mkdir(path.dirname(cmdPath), { recursive: true });
|
await mkdir(path.dirname(cmdPath), { recursive: true });
|
||||||
await writeFile(cmdPath, "@ECHO off\r\necho docker\r\n", "utf8");
|
await writeFile(cmdPath, "@ECHO off\r\necho docker\r\n", "utf8");
|
||||||
|
|
||||||
const resolved = resolveDockerSpawnInvocation(["ps"], {
|
expect(() =>
|
||||||
platform: "win32",
|
resolveDockerSpawnInvocation(["ps"], {
|
||||||
env: { PATH: dir, PATHEXT: ".CMD;.EXE;.BAT" },
|
platform: "win32",
|
||||||
execPath: "C:\\node\\node.exe",
|
env: { PATH: dir, PATHEXT: ".CMD;.EXE;.BAT" },
|
||||||
});
|
execPath: "C:\\node\\node.exe",
|
||||||
expect(path.normalize(resolved.command).toLowerCase()).toBe(
|
}),
|
||||||
path.normalize(cmdPath).toLowerCase(),
|
).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