From 48b052322bd254cdff76d0cea515f8670b7f543e Mon Sep 17 00:00:00 2001 From: Brian Mendonca Date: Tue, 24 Feb 2026 12:09:42 -0700 Subject: [PATCH] Security: sanitize inherited host exec env --- src/agents/bash-tools.exec-runtime.ts | 17 +++++++++++++++++ src/agents/bash-tools.exec.path.test.ts | 23 +++++++++++++++++++++++ src/agents/bash-tools.exec.ts | 4 +++- 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/agents/bash-tools.exec-runtime.ts b/src/agents/bash-tools.exec-runtime.ts index 2a6db0566..05973993c 100644 --- a/src/agents/bash-tools.exec-runtime.ts +++ b/src/agents/bash-tools.exec-runtime.ts @@ -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): Record { + const sanitized: Record = {}; + 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): void { diff --git a/src/agents/bash-tools.exec.path.test.ts b/src/agents/bash-tools.exec.path.test.ts index 5481ec966..041ee8672 100644 --- a/src/agents/bash-tools.exec.path.test.ts +++ b/src/agents/bash-tools.exec.path.test.ts @@ -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" }); diff --git a/src/agents/bash-tools.exec.ts b/src/agents/bash-tools.exec.ts index a9d230c24..fac68eb82 100644 --- a/src/agents/bash-tools.exec.ts +++ b/src/agents/bash-tools.exec.ts @@ -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.