fix(exec): restore runtime-aware implicit host default

This commit is contained in:
Peter Steinberger
2026-03-29 21:18:14 +01:00
parent 96df794c12
commit 5d4c4bb850
7 changed files with 26 additions and 22 deletions

View File

@@ -2574,6 +2574,7 @@ Docs: https://docs.openclaw.ai
- Security/Exec: in non-default setups that manually add `sort` to `tools.exec.safeBins`, block `sort --compress-program` so allowlist-mode safe-bin checks cannot bypass approval. Thanks @tdjackey for reporting.
- Security/Exec approvals: when users choose `allow-always` for shell-wrapper commands (for example `/bin/zsh -lc ...`), persist allowlist patterns for the inner executable(s) instead of the wrapper shell binary, preventing accidental broad shell allowlisting in moderate mode. (#23276) Thanks @xrom2863.
- Security/Exec: fail closed when `tools.exec.host=sandbox` is configured/requested but sandbox runtime is unavailable. (#23398) Thanks @bmendonca3.
- Security/Exec: restore runtime-aware implicit exec host selection so sandbox-off sessions keep defaulting to the gateway host, while explicit `host=sandbox` still fails closed without a sandbox runtime. (#56800)
- Security/macOS app beta: enforce path-only `system.run` allowlist matching (drop basename matches like `echo`), migrate legacy basename entries to last resolved paths when available, and harden shell-chain handling to fail closed on unsafe parse/control syntax (including quoted command substitution/backticks). This is an optional allowlist-mode feature; default installs remain deny-by-default. Thanks @tdjackey for reporting.
- Security/Agents: auto-generate and persist a dedicated `commands.ownerDisplaySecret` when `commands.ownerDisplay=hash`, remove gateway token fallback from owner-ID prompt hashing across CLI and embedded agent runners, and centralize owner-display secret resolution in one shared helper. Thanks @aether-ai-agent for reporting.
- Security/SSRF: expand IPv4 fetch guard blocking to include RFC special-use/non-global ranges (including benchmarking, TEST-NET, multicast, and reserved/broadcast blocks), centralize range checks into a single CIDR policy table, and reuse one shared host/IP classifier across literal + DNS checks to reduce classifier drift. Thanks @princeeismond-dot for reporting.

View File

@@ -101,7 +101,7 @@ OpenClaw does **not** model one gateway as a multi-tenant, adversarial user boun
- If multiple users need OpenClaw, use one VPS (or host/OS user boundary) per user.
- For advanced setups, multiple gateways on one machine are possible, but only with strict isolation and are not the recommended default.
- Exec behavior is host-first by default: `agents.defaults.sandbox.mode` defaults to `off`.
- `tools.exec.host` defaults to `sandbox` as a routing preference, but if sandbox runtime is not active for the session, exec runs on the gateway host.
- `tools.exec.host` defaults to `sandbox` only when sandbox runtime is active for the session; otherwise implicit exec runs on the gateway host.
- Implicit exec calls (no explicit host in the tool call) follow the same behavior.
- This is expected in OpenClaw's one-user trusted-operator model. If you need isolation, enable sandbox mode (`non-main`/`all`) and keep strict tool policy.

View File

@@ -191,7 +191,7 @@ If more than one person can DM your bot:
- **Local disk hygiene** (permissions, symlinks, config includes, “synced folder” paths).
- **Plugins** (extensions exist without an explicit allowlist).
- **Policy drift/misconfig** (sandbox docker settings configured but sandbox mode off; ineffective `gateway.nodes.denyCommands` patterns because matching is exact command-name only (for example `system.run`) and does not inspect shell text; dangerous `gateway.nodes.allowCommands` entries; global `tools.profile="minimal"` overridden by per-agent profiles; extension plugin tools reachable under permissive tool policy).
- **Runtime expectation drift** (for example `tools.exec.host="sandbox"` while sandbox mode is off, which now fails closed because no sandbox runtime is available).
- **Runtime expectation drift** (for example `tools.exec.host="sandbox"` while sandbox mode is off, which fails closed because no sandbox runtime is available).
- **Model hygiene** (warn when configured models look legacy; not a hard block).
If you run `--deep`, OpenClaw also attempts a best-effort live Gateway probe.
@@ -534,7 +534,7 @@ Even with strong system prompts, **prompt injection is not solved**. System prom
- Prefer mention gating in groups; avoid “always-on” bots in public rooms.
- Treat links, attachments, and pasted instructions as hostile by default.
- Run sensitive tool execution in a sandbox; keep secrets out of the agents reachable filesystem.
- Note: sandboxing is opt-in. If sandbox mode is off, `host=sandbox` fails closed even though tools.exec.host defaults to sandbox. To run on the gateway host, set `host=gateway` and configure exec approvals.
- Note: sandboxing is opt-in. If sandbox mode is off, explicit `host=sandbox` fails closed because no sandbox runtime is available. Implicit exec still runs on the gateway host; set `host=gateway` if you want that behavior to be explicit in config.
- Limit high-risk tools (`exec`, `browser`, `web_fetch`, `web_search`) to trusted agents or explicit allowlists.
- If you allowlist interpreters (`python`, `node`, `ruby`, `perl`, `php`, `lua`, `osascript`), enable `tools.exec.strictInlineEval` so inline eval forms still need explicit approval.
- **Model choice matters:** older/smaller/legacy models are significantly less robust against prompt injection and tool misuse. For tool-enabled agents, use the strongest latest-generation, instruction-hardened model available.

View File

@@ -29,7 +29,7 @@ Background sessions are scoped per agent; `process` only sees sessions from the
Notes:
- `host` defaults to `sandbox`.
- `host` defaults to `sandbox` when sandbox runtime is active for the session; otherwise it defaults to `gateway`.
- `elevated` forces `host=gateway`; it is only available when elevated access is enabled for the current session/provider.
- `gateway`/`node` approvals are controlled by `~/.openclaw/exec-approvals.json`.
- `node` requires a paired node (companion app or headless node host).
@@ -52,7 +52,7 @@ Notes:
- `tools.exec.notifyOnExit` (default: true): when true, backgrounded exec sessions enqueue a system event and request a heartbeat on exit.
- `tools.exec.approvalRunningNoticeMs` (default: 10000): emit a single “running” notice when an approval-gated exec runs longer than this (0 disables).
- `tools.exec.host` (default: `sandbox`)
- `tools.exec.host` (default: runtime-aware: `sandbox` when sandbox runtime is active, `gateway` otherwise)
- `tools.exec.security` (default: `deny` for sandbox, `allowlist` for gateway + node when unset)
- `tools.exec.ask` (default: `on-miss`)
- `tools.exec.node` (default: unset)

View File

@@ -251,14 +251,14 @@ describe("exec host env validation", () => {
}
});
it("fails closed when the implicit sandbox host has no sandbox runtime", async () => {
it("defaults implicit exec host to gateway when sandbox runtime is unavailable", async () => {
const tool = createExecTool({ security: "full", ask: "off" });
await expect(
tool.execute("call1", {
command: "echo ok",
}),
).rejects.toThrow(/sandbox runtime is unavailable/);
const result = await tool.execute("call1", {
command: "echo ok",
});
const output = normalizeText(result.content.find((c) => c.type === "text")?.text);
expect(output).toContain("ok");
});
it("fails closed when sandbox host is explicitly configured without sandbox runtime", async () => {

View File

@@ -329,7 +329,9 @@ export function createExecTool(
if (elevatedRequested) {
logInfo(`exec: elevated command ${truncateMiddle(params.command, 120)}`);
}
const configuredHost = defaults?.host ?? "sandbox";
// Keep the implicit host aligned with the active runtime: host-first unless a sandbox
// runtime is actually available for this session, while still honoring explicit overrides.
const configuredHost = defaults?.host ?? (defaults?.sandbox ? "sandbox" : "gateway");
const requestedHost = normalizeExecHost(params.host) ?? null;
let host: ExecHost = requestedHost ?? configuredHost;
if (!elevatedRequested && requestedHost && requestedHost !== configuredHost) {
@@ -358,8 +360,9 @@ export function createExecTool(
}
const sandbox = host === "sandbox" ? defaults?.sandbox : undefined;
// Never fall through to direct host exec when the selected host was sandbox.
if (host === "sandbox" && !sandbox) {
const sandboxHostConfigured = defaults?.host === "sandbox" || requestedHost === "sandbox";
// Never fall through to direct host exec when sandbox was selected explicitly.
if (host === "sandbox" && !sandbox && sandboxHostConfigured) {
throw new Error(
[
"exec host resolved to sandbox, but sandbox runtime is unavailable for this session.",

View File

@@ -658,21 +658,21 @@ describe("Agent-specific tool filtering", () => {
expect(resultDetails?.status).toBe("completed");
});
it("fails closed when the implicit exec host resolves to sandbox without a runtime", async () => {
it("defaults implicit exec host to gateway when sandbox runtime is unavailable", async () => {
const tools = createOpenClawCodingTools({
config: {},
sessionKey: "agent:main:main",
workspaceDir: "/tmp/test-main-implicit-sandbox",
agentDir: "/tmp/agent-main-implicit-sandbox",
workspaceDir: "/tmp/test-main-implicit-gateway",
agentDir: "/tmp/agent-main-implicit-gateway",
});
const execTool = tools.find((tool) => tool.name === "exec");
expect(execTool).toBeDefined();
await expect(
execTool!.execute("call-implicit-sandbox-default", {
command: "echo done",
}),
).rejects.toThrow("sandbox runtime is unavailable");
const result = await execTool!.execute("call-implicit-gateway-default", {
command: "echo done",
});
const resultDetails = result?.details as { status?: string } | undefined;
expect(resultDetails?.status).toBe("completed");
});
it("fails closed when exec host=sandbox is requested without sandbox runtime", async () => {