From a87a07ec8ada6e17d434306247b6258cb76eba68 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 1 Feb 2026 15:35:48 -0800 Subject: [PATCH] fix: harden host exec env validation (#4896) (thanks @HassanFleyah) --- CHANGELOG.md | 1 + docs/tools/exec.md | 12 +++++++----- src/agents/bash-tools.exec.path.test.ts | 14 ++++++++++++++ src/agents/bash-tools.exec.ts | 6 ++++++ 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95088e06ba..07198eb228 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ Docs: https://docs.openclaw.ai - Browser: secure Chrome extension relay CDP sessions. - Docker: use container port for gateway command instead of host port. (#5110) Thanks @mise42. - fix(lobster): block arbitrary exec via lobsterPath/cwd injection (GHSA-4mhr-g7xj-cg8j). (#5335) Thanks @vignesh07. +- Security: block LD_/DYLD_ env overrides for host exec. (#4896) Thanks @HassanFleyah. - Security: harden web tool content wrapping + file parsing safeguards. (#4058) Thanks @VACInc. ## 2026.1.30 diff --git a/docs/tools/exec.md b/docs/tools/exec.md index 23674dd417..cda1406ca8 100644 --- a/docs/tools/exec.md +++ b/docs/tools/exec.md @@ -36,6 +36,8 @@ Notes: - If multiple nodes are available, set `exec.node` or `tools.exec.node` to select one. - On non-Windows hosts, exec uses `SHELL` when set; if `SHELL` is `fish`, it prefers `bash` (or `sh`) from `PATH` to avoid fish-incompatible scripts, then falls back to `SHELL` if neither exists. +- Host execution (`gateway`/`node`) rejects `env.PATH` and loader overrides (`LD_*`/`DYLD_*`) to + prevent binary hijacking or injected code. - Important: sandboxing is **off by default**. If sandboxing is off, `host=sandbox` runs directly on the gateway host (no container) and **does not require approvals**. To require approvals, run with `host=gateway` and configure exec approvals (or enable sandboxing). @@ -65,16 +67,16 @@ Example: ### PATH handling -- `host=gateway`: merges your login-shell `PATH` into the exec environment (unless the exec call - already sets `env.PATH`). The daemon itself still runs with a minimal `PATH`: +- `host=gateway`: merges your login-shell `PATH` into the exec environment. `env.PATH` overrides are + rejected for host execution. The daemon itself still runs with a minimal `PATH`: - macOS: `/opt/homebrew/bin`, `/usr/local/bin`, `/usr/bin`, `/bin` - Linux: `/usr/local/bin`, `/usr/bin`, `/bin` - `host=sandbox`: runs `sh -lc` (login shell) inside the container, so `/etc/profile` may reset `PATH`. OpenClaw prepends `env.PATH` after profile sourcing via an internal env var (no shell interpolation); `tools.exec.pathPrepend` applies here too. -- `host=node`: only env overrides you pass are sent to the node. `tools.exec.pathPrepend` only applies - if the exec call already sets `env.PATH`. Headless node hosts accept `PATH` only when it prepends - the node host PATH (no replacement). macOS nodes drop `PATH` overrides entirely. +- `host=node`: only non-blocked env overrides you pass are sent to the node. `env.PATH` overrides are + rejected for host execution. Headless node hosts accept `PATH` only when it prepends the node host + PATH (no replacement). macOS nodes drop `PATH` overrides entirely. Per-agent node binding (use the agent list index in config): diff --git a/src/agents/bash-tools.exec.path.test.ts b/src/agents/bash-tools.exec.path.test.ts index 33e922ce12..2002970735 100644 --- a/src/agents/bash-tools.exec.path.test.ts +++ b/src/agents/bash-tools.exec.path.test.ts @@ -109,3 +109,17 @@ describe("exec PATH login shell merge", () => { expect(shellPathMock).not.toHaveBeenCalled(); }); }); + +describe("exec host env validation", () => { + it("blocks LD_/DYLD_ env vars on host execution", async () => { + const { createExecTool } = await import("./bash-tools.exec.js"); + const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); + + await expect( + tool.execute("call1", { + command: "echo ok", + env: { LD_DEBUG: "1" }, + }), + ).rejects.toThrow(/Security Violation: Environment variable 'LD_DEBUG' is forbidden/); + }); +}); diff --git a/src/agents/bash-tools.exec.ts b/src/agents/bash-tools.exec.ts index f510045b5a..e49b5d5792 100644 --- a/src/agents/bash-tools.exec.ts +++ b/src/agents/bash-tools.exec.ts @@ -76,6 +76,7 @@ const DANGEROUS_HOST_ENV_VARS = new Set([ "IFS", "SSLKEYLOGFILE", ]); +const DANGEROUS_HOST_ENV_PREFIXES = ["DYLD_", "LD_"]; // Centralized sanitization helper. // Throws an error if dangerous variables or PATH modifications are detected on the host. @@ -84,6 +85,11 @@ function validateHostEnv(env: Record): void { const upperKey = key.toUpperCase(); // 1. Block known dangerous variables (Fail Closed) + if (DANGEROUS_HOST_ENV_PREFIXES.some((prefix) => upperKey.startsWith(prefix))) { + throw new Error( + `Security Violation: Environment variable '${key}' is forbidden during host execution.`, + ); + } if (DANGEROUS_HOST_ENV_VARS.has(upperKey)) { throw new Error( `Security Violation: Environment variable '${key}' is forbidden during host execution.`,