diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a2337dc86d..b0510a209cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ Docs: https://docs.openclaw.ai ## Unreleased +### Fixes + +- Agents/exec: keep `/exec` current-default reporting aligned with real runtime behavior so `host=auto` sessions surface the correct host-aware fallback policy (`full/off` on gateway or node, `deny/off` on sandbox) instead of stale stricter defaults. + ## 2026.4.7 ### Changes diff --git a/src/agents/exec-defaults.test.ts b/src/agents/exec-defaults.test.ts index a7321639145..b849a8b6f21 100644 --- a/src/agents/exec-defaults.test.ts +++ b/src/agents/exec-defaults.test.ts @@ -1,8 +1,17 @@ -import { describe, expect, it } from "vitest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; import type { SessionEntry } from "../config/sessions.js"; +import * as execApprovals from "../infra/exec-approvals.js"; import { resolveExecDefaults } from "./exec-defaults.js"; describe("resolveExecDefaults", () => { + beforeEach(() => { + vi.restoreAllMocks(); + vi.spyOn(execApprovals, "loadExecApprovals").mockReturnValue({ + version: 1, + agents: {}, + }); + }); + it("does not advertise node routing when exec host is pinned to gateway", () => { expect( resolveExecDefaults({ @@ -55,4 +64,44 @@ describe("resolveExecDefaults", () => { }).canRequestNode, ).toBe(true); }); + + it("uses host approval defaults for gateway when exec policy is unset", () => { + expect( + resolveExecDefaults({ + cfg: { + tools: { + exec: { + host: "auto", + }, + }, + }, + sandboxAvailable: false, + }), + ).toMatchObject({ + host: "auto", + effectiveHost: "gateway", + security: "full", + ask: "off", + }); + }); + + it("keeps sandbox deny by default when auto resolves to sandbox", () => { + expect( + resolveExecDefaults({ + cfg: { + tools: { + exec: { + host: "auto", + }, + }, + }, + sandboxAvailable: true, + }), + ).toMatchObject({ + host: "auto", + effectiveHost: "sandbox", + security: "deny", + ask: "off", + }); + }); }); diff --git a/src/agents/exec-defaults.ts b/src/agents/exec-defaults.ts index 4b46048e94d..62925a4b103 100644 --- a/src/agents/exec-defaults.ts +++ b/src/agents/exec-defaults.ts @@ -1,6 +1,12 @@ import type { OpenClawConfig } from "../config/config.js"; import type { SessionEntry } from "../config/sessions.js"; -import type { ExecAsk, ExecHost, ExecSecurity, ExecTarget } from "../infra/exec-approvals.js"; +import { + loadExecApprovals, + type ExecAsk, + type ExecHost, + type ExecSecurity, + type ExecTarget, +} from "../infra/exec-approvals.js"; import { resolveAgentConfig, resolveSessionAgentId } from "./agent-scope.js"; import { isRequestedExecTargetAllowed, resolveExecTarget } from "./bash-tools.exec-runtime.js"; import { resolveSandboxRuntimeStatus } from "./sandbox/runtime-status.js"; @@ -88,6 +94,8 @@ export function resolveExecDefaults(params: { elevatedRequested: false, sandboxAvailable, }); + const approvalDefaults = loadExecApprovals().defaults; + const defaultSecurity = resolved.effectiveHost === "sandbox" ? "deny" : "full"; return { host, effectiveHost: resolved.effectiveHost, @@ -95,12 +103,14 @@ export function resolveExecDefaults(params: { (params.sessionEntry?.execSecurity as ExecSecurity | undefined) ?? agentExec?.security ?? globalExec?.security ?? - "deny", + approvalDefaults?.security ?? + defaultSecurity, ask: (params.sessionEntry?.execAsk as ExecAsk | undefined) ?? agentExec?.ask ?? globalExec?.ask ?? - "on-miss", + approvalDefaults?.ask ?? + "off", node: params.sessionEntry?.execNode ?? agentExec?.node ?? globalExec?.node, canRequestNode: isRequestedExecTargetAllowed({ configuredTarget: host,