mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:20:43 +00:00
fix: allow safe Windows companion node commands (#71884)
Merged via squash.
Prepared head SHA: 24e2b79fe4
Co-authored-by: shanselman <2892+shanselman@users.noreply.github.com>
Co-authored-by: shanselman <2892+shanselman@users.noreply.github.com>
Reviewed-by: @shanselman
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -188,6 +188,23 @@ openclaw nodes invoke --node <idOrNameOrIp> --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 }`.
|
||||
|
||||
@@ -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(
|
||||
{
|
||||
|
||||
@@ -115,7 +115,14 @@ const PLATFORM_DEFAULTS: Record<string, string[]> = {
|
||||
...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],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user