From c9c5bebb4ffbd21c707807c176f7d1133599e5c2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 13:17:36 +0000 Subject: [PATCH 1/2] fix(security): stop echoing ex.Message into node capability error responses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exception messages in node capability error responses can contain sensitive data: file paths, device names, API key prefixes, environment variable values, or command output fragments. These responses flow back through the gateway to the chat agent and may be persisted in recent activity / support bundles. The new SttCapability (PR #288) explicitly documents the privacy invariant: "never echo raw exception text into the response; full detail stays in the local log only". This commit applies the same rule to the existing capabilities that were not yet following it. Affected capabilities: - SystemCapability: system.run execution errors (can expose env vars/output), V2 approval handler exceptions, execApprovals.set policy update errors - CameraCapability: camera.list / camera.snap / camera.clip errors (can expose device names and paths) - ScreenCapability: screen.snapshot / screen.record errors (can expose paths) - LocationCapability: location.get errors (can expose system paths) Not changed: - TtsCapability: already fixed by the in-flight PR #288 - BrowserProxyCapability: intentionally includes connectivity context (port/host reachability) which is user-facing diagnostic data - CanvasCapability: mixed — some errors are intentional UI feedback Exception detail is preserved in Logger.Error calls that were already present, so diagnostics remain available in local logs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/OpenClaw.Shared/Capabilities/CameraCapability.cs | 6 +++--- src/OpenClaw.Shared/Capabilities/LocationCapability.cs | 2 +- src/OpenClaw.Shared/Capabilities/ScreenCapability.cs | 4 ++-- src/OpenClaw.Shared/Capabilities/SystemCapability.cs | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/OpenClaw.Shared/Capabilities/CameraCapability.cs b/src/OpenClaw.Shared/Capabilities/CameraCapability.cs index aaddf170..547a5bed 100644 --- a/src/OpenClaw.Shared/Capabilities/CameraCapability.cs +++ b/src/OpenClaw.Shared/Capabilities/CameraCapability.cs @@ -60,7 +60,7 @@ private async Task HandleListAsync(NodeInvokeRequest request catch (Exception ex) { Logger.Error("Camera list failed", ex); - return Error($"List failed: {ex.Message}"); + return Error("List failed"); } } @@ -106,7 +106,7 @@ private async Task HandleSnapAsync(NodeInvokeRequest request catch (Exception ex) { Logger.Error("Camera snap failed", ex); - return Error($"Snap failed: {ex.Message}"); + return Error("Snap failed"); } } @@ -147,7 +147,7 @@ private async Task HandleClipAsync(NodeInvokeRequest request catch (Exception ex) { Logger.Error("Camera clip failed", ex); - return Error($"Clip failed: {ex.Message}"); + return Error("Clip failed"); } } } diff --git a/src/OpenClaw.Shared/Capabilities/LocationCapability.cs b/src/OpenClaw.Shared/Capabilities/LocationCapability.cs index 93b8309c..832e0cbf 100644 --- a/src/OpenClaw.Shared/Capabilities/LocationCapability.cs +++ b/src/OpenClaw.Shared/Capabilities/LocationCapability.cs @@ -64,7 +64,7 @@ private async Task HandleGetAsync(NodeInvokeRequest request) catch (Exception ex) { Logger.Error("location.get failed", ex); - return Error($"Location failed: {ex.Message}"); + return Error("Location failed"); } } } diff --git a/src/OpenClaw.Shared/Capabilities/ScreenCapability.cs b/src/OpenClaw.Shared/Capabilities/ScreenCapability.cs index 8d07accb..b81bf19a 100644 --- a/src/OpenClaw.Shared/Capabilities/ScreenCapability.cs +++ b/src/OpenClaw.Shared/Capabilities/ScreenCapability.cs @@ -84,7 +84,7 @@ private async Task HandleCaptureAsync(NodeInvokeRequest requ catch (Exception ex) { Logger.Error("Screen capture failed", ex); - return Error($"Capture failed: {ex.Message}"); + return Error("Capture failed"); } } @@ -134,7 +134,7 @@ private async Task HandleRecordAsync(NodeInvokeRequest reque catch (Exception ex) { Logger.Error("Screen recording failed", ex); - return Error($"Recording failed: {ex.Message}"); + return Error("Recording failed"); } } diff --git a/src/OpenClaw.Shared/Capabilities/SystemCapability.cs b/src/OpenClaw.Shared/Capabilities/SystemCapability.cs index 91d0e1af..3526bd8f 100644 --- a/src/OpenClaw.Shared/Capabilities/SystemCapability.cs +++ b/src/OpenClaw.Shared/Capabilities/SystemCapability.cs @@ -271,7 +271,7 @@ private async Task HandleRunAsync(NodeInvokeRequest request) { // Rail 1: no silent fallback — handler exceptions become typed denies. Logger.Error($"[system.run] corr={correlationId} path=v2 handler threw", ex); - v2Result = ExecApprovalV2Result.ValidationFailed($"Handler exception: {ex.Message}"); + v2Result = ExecApprovalV2Result.ValidationFailed("Handler exception"); } Logger.Info($"[system.run] corr={correlationId} decision={v2Result.Code} reason={v2Result.Reason}"); @@ -413,7 +413,7 @@ private async Task HandleRunAsync(NodeInvokeRequest request) catch (Exception ex) { Logger.Error("system.run failed", ex); - return Error($"Execution failed: {ex.Message}"); + return Error("Execution failed"); } } @@ -614,7 +614,7 @@ private NodeInvokeResponse HandleExecApprovalsSet(NodeInvokeRequest request) catch (Exception ex) { Logger.Error("execApprovals.set failed", ex); - return Error($"Failed to update policy: {ex.Message}"); + return Error("Failed to update policy"); } } From cdd200b349c6e8295ce185569561337602f06583 Mon Sep 17 00:00:00 2001 From: Scott Hanselman Date: Thu, 7 May 2026 14:08:31 -0400 Subject: [PATCH 2/2] test: assert sanitized capability errors Update capability exception tests to verify generic node responses after raw exception details are kept in local logs only. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/OpenClaw.Shared.Tests/CapabilityTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/OpenClaw.Shared.Tests/CapabilityTests.cs b/tests/OpenClaw.Shared.Tests/CapabilityTests.cs index f79c25cd..a5bd4ecf 100644 --- a/tests/OpenClaw.Shared.Tests/CapabilityTests.cs +++ b/tests/OpenClaw.Shared.Tests/CapabilityTests.cs @@ -2082,7 +2082,7 @@ public async Task Capture_ReturnsError_WhenHandlerThrows() var req = new NodeInvokeRequest { Id = "s5", Command = "screen.snapshot", Args = Parse("""{}""") }; var res = await cap.ExecuteAsync(req); Assert.False(res.Ok); - Assert.Contains("Display access denied", res.Error); + Assert.Equal("Capture failed", res.Error); } [Fact] @@ -2327,7 +2327,7 @@ public async Task Record_ReturnsError_WhenHandlerThrows() var req = new NodeInvokeRequest { Id = "s15", Command = "screen.record", Args = Parse("""{}""") }; var res = await cap.ExecuteAsync(req); Assert.False(res.Ok); - Assert.Contains("Capture permission denied", res.Error); + Assert.Equal("Recording failed", res.Error); } } @@ -2457,7 +2457,7 @@ public async Task Snap_ReturnsError_WhenHandlerThrows() var req = new NodeInvokeRequest { Id = "cam6", Command = "camera.snap", Args = Parse("""{}""") }; var res = await cap.ExecuteAsync(req); Assert.False(res.Ok); - Assert.Contains("Camera access blocked", res.Error); + Assert.Equal("Snap failed", res.Error); } [Fact] @@ -2892,7 +2892,7 @@ public async Task Get_ReturnsError_WhenHandlerThrows() var req = new NodeInvokeRequest { Id = "loc6", Command = "location.get", Args = Parse("""{}""") }; var res = await cap.ExecuteAsync(req); Assert.False(res.Ok); - Assert.Contains("GPS unavailable", res.Error); + Assert.Equal("Location failed", res.Error); } [Fact]