Security: sanitize inherited host exec env

This commit is contained in:
Brian Mendonca
2026-02-24 12:09:42 -07:00
committed by Peter Steinberger
parent 9514201fb9
commit 48b052322b
3 changed files with 43 additions and 1 deletions

View File

@@ -29,6 +29,23 @@ import {
import { buildCursorPositionResponse, stripDsrRequests } from "./pty-dsr.js";
import { getShellConfig, sanitizeBinaryOutput } from "./shell-utils.js";
// Sanitize inherited host env before merge so dangerous variables from process.env
// are not propagated into non-sandboxed executions.
export function sanitizeHostBaseEnv(env: Record<string, string>): Record<string, string> {
const sanitized: Record<string, string> = {};
for (const [key, value] of Object.entries(env)) {
const upperKey = key.toUpperCase();
if (upperKey === "PATH") {
sanitized[key] = value;
continue;
}
if (isDangerousHostEnvVarName(upperKey)) {
continue;
}
sanitized[key] = value;
}
return sanitized;
}
// Centralized sanitization helper.
// Throws an error if dangerous variables or PATH modifications are detected on the host.
export function validateHostEnv(env: Record<string, string>): void {

View File

@@ -166,6 +166,29 @@ describe("exec host env validation", () => {
).rejects.toThrow(/Security Violation: Environment variable 'LD_DEBUG' is forbidden/);
});
it("strips dangerous inherited env vars from host execution", async () => {
if (isWin) {
return;
}
const original = process.env.SSLKEYLOGFILE;
process.env.SSLKEYLOGFILE = "/tmp/openclaw-ssl-keys.log";
try {
const { createExecTool } = await import("./bash-tools.exec.js");
const tool = createExecTool({ host: "gateway", security: "full", ask: "off" });
const result = await tool.execute("call1", {
command: "printf '%s' \"${SSLKEYLOGFILE:-}\"",
});
const output = normalizeText(result.content.find((c) => c.type === "text")?.text);
expect(output).not.toContain("/tmp/openclaw-ssl-keys.log");
} finally {
if (original === undefined) {
delete process.env.SSLKEYLOGFILE;
} else {
process.env.SSLKEYLOGFILE = original;
}
}
});
it("defaults to sandbox when sandbox runtime is unavailable", async () => {
const tool = createExecTool({ security: "full", ask: "off" });

View File

@@ -25,6 +25,7 @@ import {
renderExecHostLabel,
resolveApprovalRunningNoticeMs,
runExecProcess,
sanitizeHostBaseEnv,
execSchema,
validateHostEnv,
} from "./bash-tools.exec-runtime.js";
@@ -359,7 +360,8 @@ export function createExecTool(
workdir = resolveWorkdir(rawWorkdir, warnings);
}
const baseEnv = coerceEnv(process.env);
const inheritedBaseEnv = coerceEnv(process.env);
const baseEnv = host === "sandbox" ? inheritedBaseEnv : sanitizeHostBaseEnv(inheritedBaseEnv);
// Logic: Sandbox gets raw env. Host (gateway/node) must pass validation.
// We validate BEFORE merging to prevent any dangerous vars from entering the stream.