From 8f277e4b7f4f06a4cd7825a39b3739d396fb942c Mon Sep 17 00:00:00 2001 From: Scott Hanselman Date: Tue, 28 Apr 2026 02:01:20 -0700 Subject: [PATCH] fix: allow safe Windows companion node commands (#71884) Merged via squash. Prepared head SHA: 24e2b79fe45a3e9d2338f7d16aded327e5a7ca40 Co-authored-by: shanselman <2892+shanselman@users.noreply.github.com> Co-authored-by: shanselman <2892+shanselman@users.noreply.github.com> Reviewed-by: @shanselman --- CHANGELOG.md | 4 ++++ docs/gateway/configuration-reference.md | 2 +- docs/nodes/index.md | 17 +++++++++++++++++ src/gateway/gateway-misc.test.ts | 25 +++++++++++++++++++++++++ src/gateway/node-command-policy.ts | 9 ++++++++- 5 files changed, 55 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f2f8b4b2e2..91d8755e143 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,10 @@ Docs: https://docs.openclaw.ai - Agents/failover: seed non-claude-cli fallback prompts with Claude Code session context when a claude-cli attempt fails, so fallback models do not restart cold after billing or quota failover. (#72069) Thanks @stainlu. - Agents/CLI runner: transfer bundle-MCP tempDir cleanup from the per-turn runner finally to the Claude live-session lifecycle, so persistent Claude CLI sessions keep their `--mcp-config` directory until the live subprocess closes. Fixes #73244. Thanks @edwin-rivera-dev. +### Fixes + +- Gateway/nodes: allow Windows companion nodes to use safe declared commands such as canvas, camera list, location, device info, and screen snapshot by default while keeping dangerous media commands opt-in. (#71884) Thanks @shanselman. + ## 2026.4.27 ### Changes diff --git a/docs/gateway/configuration-reference.md b/docs/gateway/configuration-reference.md index d5693d1f5e8..f0112b69be8 100644 --- a/docs/gateway/configuration-reference.md +++ b/docs/gateway/configuration-reference.md @@ -451,7 +451,7 @@ See [Plugins](/tools/plugin). - `trustedProxies`: reverse proxy IPs that terminate TLS or inject forwarded-client headers. Only list proxies you control. Loopback entries are still valid for same-host proxy/local-detection setups (for example Tailscale Serve or a local reverse proxy), but they do **not** make loopback requests eligible for `gateway.auth.mode: "trusted-proxy"`. - `allowRealIpFallback`: when `true`, the gateway accepts `X-Real-IP` if `X-Forwarded-For` is missing. Default `false` for fail-closed behavior. - `gateway.nodes.pairing.autoApproveCidrs`: optional CIDR/IP allowlist for auto-approving first-time node device pairing with no requested scopes. It is disabled when unset. This does not auto-approve operator/browser/Control UI/WebChat pairing, and it does not auto-approve role, scope, metadata, or public-key upgrades. -- `gateway.nodes.allowCommands` / `gateway.nodes.denyCommands`: global allow/deny shaping for declared node commands after pairing and allowlist evaluation. +- `gateway.nodes.allowCommands` / `gateway.nodes.denyCommands`: global allow/deny shaping for declared node commands after pairing and platform allowlist evaluation. Use `allowCommands` to opt into dangerous node commands such as `camera.snap`, `camera.clip`, and `screen.record`; `denyCommands` removes a command even if a platform default or explicit allow would otherwise include it. After a node changes its declared command list, reject and re-approve that device pairing so the gateway stores the updated command snapshot. - `gateway.tools.deny`: extra tool names blocked for HTTP `POST /tools/invoke` (extends default deny list). - `gateway.tools.allow`: remove tool names from the default HTTP deny list. diff --git a/docs/nodes/index.md b/docs/nodes/index.md index 7b5f311d7d7..3dcd86429fc 100644 --- a/docs/nodes/index.md +++ b/docs/nodes/index.md @@ -188,6 +188,23 @@ openclaw nodes invoke --node --command canvas.eval --params '{"ja Higher-level helpers exist for the common “give the agent a MEDIA attachment” workflows. +## Command policy + +Node commands must pass two gates before they can be invoked: + +1. The node must declare the command in its WebSocket `connect.commands` list. +2. The gateway's platform policy must allow the declared command. + +Windows and macOS companion nodes allow safe declared commands such as +`canvas.*`, `camera.list`, `location.get`, and `screen.snapshot` by default. +Dangerous or privacy-heavy commands such as `camera.snap`, `camera.clip`, and +`screen.record` still require explicit opt-in with +`gateway.nodes.allowCommands`. `gateway.nodes.denyCommands` always wins over +defaults and extra allowlist entries. + +After a node changes its declared command list, reject the old device pairing +and approve the new request so the gateway stores the updated command snapshot. + ## Screenshots (canvas snapshots) If the node is showing the Canvas (WebView), `canvas.snapshot` returns `{ format, base64 }`. diff --git a/src/gateway/gateway-misc.test.ts b/src/gateway/gateway-misc.test.ts index dad49cf23de..cd33b0a7476 100644 --- a/src/gateway/gateway-misc.test.ts +++ b/src/gateway/gateway-misc.test.ts @@ -718,6 +718,31 @@ describe("resolveNodeCommandAllowlist", () => { expect(allow.has("screen.record")).toBe(false); }); + it("allows safe Windows companion commands by default but keeps dangerous media gated", () => { + const allow = resolveNodeCommandAllowlist( + {}, + { + platform: "Windows_NT", + deviceFamily: "Windows", + }, + ); + + expect(allow.has("canvas.present")).toBe(true); + expect(allow.has("canvas.a2ui.pushJSONL")).toBe(true); + expect(allow.has("camera.list")).toBe(true); + expect(allow.has("location.get")).toBe(true); + expect(allow.has("device.info")).toBe(true); + expect(allow.has("device.status")).toBe(true); + expect(allow.has("screen.snapshot")).toBe(true); + expect(allow.has("system.run")).toBe(true); + expect(allow.has("system.which")).toBe(true); + expect(allow.has("system.notify")).toBe(true); + + for (const cmd of DEFAULT_DANGEROUS_NODE_COMMANDS) { + expect(allow.has(cmd)).toBe(false); + } + }); + it("can explicitly allow dangerous commands via allowCommands", () => { const allow = resolveNodeCommandAllowlist( { diff --git a/src/gateway/node-command-policy.ts b/src/gateway/node-command-policy.ts index 96a20a940c1..d6a12915335 100644 --- a/src/gateway/node-command-policy.ts +++ b/src/gateway/node-command-policy.ts @@ -115,7 +115,14 @@ const PLATFORM_DEFAULTS: Record = { ...SCREEN_COMMANDS, ], linux: [...SYSTEM_COMMANDS], - windows: [...SYSTEM_COMMANDS], + windows: [ + ...CANVAS_COMMANDS, + ...CAMERA_COMMANDS, + ...LOCATION_COMMANDS, + ...DEVICE_COMMANDS, + ...SYSTEM_COMMANDS, + ...SCREEN_COMMANDS, + ], // Fail-safe: unknown metadata should not receive host exec defaults. unknown: [...UNKNOWN_PLATFORM_COMMANDS], };