From 1cbe6e271bae1aa6a3e10eeab4a9fde37517e9bd Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 11 May 2026 12:54:55 +0100 Subject: [PATCH] fix(exec): address security floor review --- docs/tools/exec.md | 4 ++- .../bash-tools.exec.security-floor.test.ts | 30 ++++++++++++++++++- src/agents/bash-tools.schemas.ts | 3 +- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/docs/tools/exec.md b/docs/tools/exec.md index 17baa8e5f90..c86bada78a4 100644 --- a/docs/tools/exec.md +++ b/docs/tools/exec.md @@ -46,7 +46,9 @@ Where to execute. `auto` resolves to `sandbox` when a sandbox runtime is active -Enforcement mode for `gateway` / `node` execution. +Ignored for normal tool calls. `gateway` / `node` security is controlled by +`tools.exec.security` and `~/.openclaw/exec-approvals.json`; elevated mode can +force `security=full` only when the operator explicitly grants elevated access. diff --git a/src/agents/bash-tools.exec.security-floor.test.ts b/src/agents/bash-tools.exec.security-floor.test.ts index 72a7b7e1982..e72ca98bd8e 100644 --- a/src/agents/bash-tools.exec.security-floor.test.ts +++ b/src/agents/bash-tools.exec.security-floor.test.ts @@ -1,3 +1,6 @@ +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { captureEnv } from "../test-utils/env.js"; import { resetProcessRegistryForTests } from "./bash-process-registry.js"; @@ -5,14 +8,39 @@ import { createExecTool } from "./bash-tools.exec.js"; describe("exec security floor", () => { let envSnapshot: ReturnType; + let tempRoot: string | undefined; beforeEach(() => { - envSnapshot = captureEnv(["SHELL"]); + envSnapshot = captureEnv([ + "HOME", + "USERPROFILE", + "HOMEDRIVE", + "HOMEPATH", + "OPENCLAW_STATE_DIR", + "SHELL", + ]); + tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-exec-security-floor-")); + process.env.HOME = tempRoot; + process.env.USERPROFILE = tempRoot; + process.env.OPENCLAW_STATE_DIR = path.join(tempRoot, "state"); + if (process.platform === "win32") { + const parsed = path.parse(tempRoot); + process.env.HOMEDRIVE = parsed.root.slice(0, 2); + process.env.HOMEPATH = tempRoot.slice(2) || "\\"; + } else { + delete process.env.HOMEDRIVE; + delete process.env.HOMEPATH; + } resetProcessRegistryForTests(); }); afterEach(() => { + const dir = tempRoot; + tempRoot = undefined; envSnapshot.restore(); + if (dir) { + fs.rmSync(dir, { recursive: true, force: true }); + } }); it("ignores model-supplied allowlist security when configured security is full", async () => { diff --git a/src/agents/bash-tools.schemas.ts b/src/agents/bash-tools.schemas.ts index 80fe33a9f01..a0e9e8089ad 100644 --- a/src/agents/bash-tools.schemas.ts +++ b/src/agents/bash-tools.schemas.ts @@ -34,7 +34,8 @@ export const execSchema = Type.Object({ }), security: Type.Optional( Type.String({ - description: "Exec security mode (deny|allowlist|full).", + description: + "Ignored for normal calls; exec security is set by tools.exec.security and host approvals.", }), ), ask: Type.Optional(