From 17f377e04cb59232eb2c84ca20bc4d4827bbf4ad Mon Sep 17 00:00:00 2001 From: Scott Hanselman Date: Mon, 27 Apr 2026 02:27:52 -0700 Subject: [PATCH] fix: diagnose blocked browser proxy policy Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/MISSION_CONTROL.md | 1 + src/OpenClaw.Shared/Models.cs | 32 ++++++++++++++++- .../Windows/StatusDetailWindow.xaml.cs | 6 ++++ tests/OpenClaw.Shared.Tests/ModelsTests.cs | 35 +++++++++++++++++++ .../TrayMenuWindowMarkupTests.cs | 2 ++ 5 files changed, 75 insertions(+), 1 deletion(-) diff --git a/docs/MISSION_CONTROL.md b/docs/MISSION_CONTROL.md index 00aa82d..41c4383 100644 --- a/docs/MISSION_CONTROL.md +++ b/docs/MISSION_CONTROL.md @@ -435,6 +435,7 @@ Deliverables: - Dangerous command opt-in guidance: **implemented copyable safety guidance for camera/screen privacy-sensitive commands without emitting one-click dangerous repair commands** - Node capability settings: **implemented Settings toggles for canvas, screen, camera, location, and browser proxy command groups so privacy-sensitive surfaces can be disabled before reconnecting/re-pairing** - Disabled capability diagnostics: **implemented Command Center distinction between intentionally disabled Settings groups and true gateway allowlist/parity gaps** +- Browser proxy policy diagnostics: **implemented a specific Command Center warning/copy action for declared `browser.proxy` commands filtered by gateway policy, instead of burying them under generic blocked-command output** Risk: high for exec/security. Do not rush. diff --git a/src/OpenClaw.Shared/Models.cs b/src/OpenClaw.Shared/Models.cs index 8256876..dc6c8c1 100644 --- a/src/OpenClaw.Shared/Models.cs +++ b/src/OpenClaw.Shared/Models.cs @@ -845,10 +845,12 @@ public class NodeCapabilityHealthInfo public Dictionary Permissions { get; set; } = new(StringComparer.OrdinalIgnoreCase); public List SafeDeclaredCommands { get; set; } = new(); public List DangerousDeclaredCommands { get; set; } = new(); + public List BrowserDeclaredCommands { get; set; } = new(); public List WindowsSpecificDeclaredCommands { get; set; } = new(); public List BlockedDeclaredCommands { get; set; } = new(); public List MissingSafeAllowlistCommands { get; set; } = new(); public List MissingDangerousAllowlistCommands { get; set; } = new(); + public List MissingBrowserAllowlistCommands { get; set; } = new(); public List MissingMacParityCommands { get; set; } = new(); public List DisabledBySettingsCommands { get; set; } = new(); public List Warnings { get; set; } = new(); @@ -879,6 +881,9 @@ public class NodeCapabilityHealthInfo DangerousDeclaredCommands = CommandCenterCommandGroups.DangerousCommands .Where(commandSet.Contains) .ToList(), + BrowserDeclaredCommands = CommandCenterCommandGroups.BrowserCommands + .Where(commandSet.Contains) + .ToList(), WindowsSpecificDeclaredCommands = CommandCenterCommandGroups.WindowsSpecificCommands .Where(commandSet.Contains) .ToList() @@ -894,6 +899,8 @@ public class NodeCapabilityHealthInfo info.MissingSafeAllowlistCommands.Add(command); else if (CommandCenterCommandGroups.DangerousCommandSet.Contains(command)) info.MissingDangerousAllowlistCommands.Add(command); + else if (CommandCenterCommandGroups.BrowserCommandSet.Contains(command)) + info.MissingBrowserAllowlistCommands.Add(command); } if (isWindows) @@ -991,6 +998,14 @@ public static class CommandCenterCommandGroups public static readonly FrozenSet DangerousCommandSet = DangerousCommands.ToFrozenSet(StringComparer.OrdinalIgnoreCase); + public static readonly string[] BrowserCommands = + [ + "browser.proxy" + ]; + + public static readonly FrozenSet BrowserCommandSet = + BrowserCommands.ToFrozenSet(StringComparer.OrdinalIgnoreCase); + public static readonly string[] WindowsSpecificCommands = [ "system.execApprovals.get", @@ -1192,6 +1207,20 @@ public static class CommandCenterDiagnostics }); } + if (node.MissingBrowserAllowlistCommands.Count > 0) + { + var blocked = string.Join(", ", node.MissingBrowserAllowlistCommands); + warnings.Add(new GatewayDiagnosticWarning + { + Severity = GatewayDiagnosticSeverity.Warning, + Category = "allowlist", + Title = "Browser proxy command is filtered by gateway policy", + Detail = $"{blocked} {(node.MissingBrowserAllowlistCommands.Count == 1 ? "is" : "are")} declared by the node but not allowed by gateway policy. Add the exact browser command and re-approve or re-pair the node if the gateway keeps an older command snapshot.", + RepairAction = "Copy browser proxy allowlist repair command", + CopyText = BuildAllowCommandsRepairCommand(node.MissingBrowserAllowlistCommands) + }); + } + if (node.DisabledBySettingsCommands.Count > 0) { warnings.Add(new GatewayDiagnosticWarning @@ -1205,7 +1234,8 @@ public static class CommandCenterDiagnostics if (node.BlockedDeclaredCommands.Count > 0 && node.MissingSafeAllowlistCommands.Count == 0 && - node.MissingDangerousAllowlistCommands.Count == 0) + node.MissingDangerousAllowlistCommands.Count == 0 && + node.MissingBrowserAllowlistCommands.Count == 0) { warnings.Add(new GatewayDiagnosticWarning { diff --git a/src/OpenClaw.Tray.WinUI/Windows/StatusDetailWindow.xaml.cs b/src/OpenClaw.Tray.WinUI/Windows/StatusDetailWindow.xaml.cs index 4e4603f..ca2d860 100644 --- a/src/OpenClaw.Tray.WinUI/Windows/StatusDetailWindow.xaml.cs +++ b/src/OpenClaw.Tray.WinUI/Windows/StatusDetailWindow.xaml.cs @@ -693,11 +693,13 @@ public sealed partial class StatusDetailWindow : WindowEx builder.AppendLine($" declared commands: {FormatCommandList(node.Commands)}"); builder.AppendLine($" safe companion commands: {FormatCommandList(node.SafeDeclaredCommands)}"); builder.AppendLine($" privacy-sensitive opt-ins: {FormatCommandList(node.DangerousDeclaredCommands)}"); + builder.AppendLine($" browser proxy commands: {FormatCommandList(node.BrowserDeclaredCommands)}"); builder.AppendLine($" Windows-specific commands: {FormatCommandList(node.WindowsSpecificDeclaredCommands)}"); builder.AppendLine($" filtered by gateway policy: {FormatCommandList(node.BlockedDeclaredCommands)}"); builder.AppendLine($" disabled in Settings: {FormatCommandList(node.DisabledBySettingsCommands)}"); builder.AppendLine($" missing safe allowlist: {FormatCommandList(node.MissingSafeAllowlistCommands)}"); builder.AppendLine($" missing privacy-sensitive allowlist: {FormatCommandList(node.MissingDangerousAllowlistCommands)}"); + builder.AppendLine($" missing browser proxy allowlist: {FormatCommandList(node.MissingBrowserAllowlistCommands)}"); builder.AppendLine($" missing Mac parity: {FormatCommandList(node.MissingMacParityCommands)}"); } @@ -842,6 +844,8 @@ public sealed partial class StatusDetailWindow : WindowEx parts.Add($"{node.SafeDeclaredCommands.Count} safe"); if (node.DangerousDeclaredCommands.Count > 0) parts.Add($"{node.DangerousDeclaredCommands.Count} opt-in"); + if (node.BrowserDeclaredCommands.Count > 0) + parts.Add("browser.proxy"); if (node.WindowsSpecificDeclaredCommands.Count > 0) parts.Add($"{node.WindowsSpecificDeclaredCommands.Count} Windows"); if (node.DisabledBySettingsCommands.Count > 0) @@ -865,8 +869,10 @@ public sealed partial class StatusDetailWindow : WindowEx builder.AppendLine(BuildNodeSummary(node).TrimEnd()); builder.AppendLine($"Safe companion commands: {FormatCommandList(node.SafeDeclaredCommands)}"); builder.AppendLine($"Privacy-sensitive commands: {FormatCommandList(node.DangerousDeclaredCommands)}"); + builder.AppendLine($"Browser proxy commands: {FormatCommandList(node.BrowserDeclaredCommands)}"); builder.AppendLine($"Windows-specific commands: {FormatCommandList(node.WindowsSpecificDeclaredCommands)}"); builder.AppendLine($"Filtered by gateway policy: {FormatCommandList(node.BlockedDeclaredCommands)}"); + builder.AppendLine($"Missing browser proxy allowlist: {FormatCommandList(node.MissingBrowserAllowlistCommands)}"); builder.AppendLine($"Disabled in Settings: {FormatCommandList(node.DisabledBySettingsCommands)}"); builder.AppendLine($"Missing Mac parity: {FormatCommandList(node.MissingMacParityCommands)}"); builder.AppendLine(); diff --git a/tests/OpenClaw.Shared.Tests/ModelsTests.cs b/tests/OpenClaw.Shared.Tests/ModelsTests.cs index 745d1f2..2d80e48 100644 --- a/tests/OpenClaw.Shared.Tests/ModelsTests.cs +++ b/tests/OpenClaw.Shared.Tests/ModelsTests.cs @@ -918,6 +918,7 @@ public class CommandCenterModelTests Assert.Contains("device.info", CommandCenterCommandGroups.SafeCompanionCommands); Assert.Contains("device.status", CommandCenterCommandGroups.SafeCompanionCommands); Assert.Contains("screen.record", CommandCenterCommandGroups.DangerousCommands); + Assert.Contains("browser.proxy", CommandCenterCommandGroups.BrowserCommands); Assert.Contains("browser.proxy", CommandCenterCommandGroups.MacNodeParityCommands); } @@ -1075,6 +1076,40 @@ public class CommandCenterModelTests !string.IsNullOrWhiteSpace(w.CopyText)); } + [Fact] + public void NodeCapabilityHealthInfo_WarnsSpecificallyForBlockedBrowserProxy() + { + var node = new GatewayNodeInfo + { + NodeId = "node-1", + DisplayName = "Windows Node", + Platform = "windows", + IsOnline = true, + Commands = + [ + "system.notify", + "system.run", + "system.which", + "browser.proxy" + ], + Permissions = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + ["browser.proxy"] = false + } + }; + + var info = NodeCapabilityHealthInfo.FromNode(node); + + Assert.Contains("browser.proxy", info.BrowserDeclaredCommands); + Assert.Contains("browser.proxy", info.MissingBrowserAllowlistCommands); + Assert.DoesNotContain("browser.proxy", info.MissingMacParityCommands); + Assert.Contains(info.Warnings, w => + w.Title == "Browser proxy command is filtered by gateway policy" && + w.RepairAction == "Copy browser proxy allowlist repair command" && + w.CopyText == "openclaw config set gateway.nodes.allowCommands '[\"browser.proxy\"]'"); + Assert.DoesNotContain(info.Warnings, w => w.Title == "Some node commands are filtered"); + } + [Fact] public void NodeCapabilityHealthInfo_TreatsDisabledCommandsAsSettingsChoice() { diff --git a/tests/OpenClaw.Tray.Tests/TrayMenuWindowMarkupTests.cs b/tests/OpenClaw.Tray.Tests/TrayMenuWindowMarkupTests.cs index 993cae6..dd59eb3 100644 --- a/tests/OpenClaw.Tray.Tests/TrayMenuWindowMarkupTests.cs +++ b/tests/OpenClaw.Tray.Tests/TrayMenuWindowMarkupTests.cs @@ -176,6 +176,8 @@ public class TrayMenuWindowMarkupTests Assert.Contains("OpenClaw node inventory", source); Assert.Contains("Safe companion commands", source); Assert.Contains("Privacy-sensitive commands", source); + Assert.Contains("Browser proxy commands", source); + Assert.Contains("Missing browser proxy allowlist", source); Assert.Contains("Disabled in Settings", source); Assert.Contains("Missing Mac parity", source); }