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(