Skip to content

Commit 4d8d43b

Browse files
shanselmanCopilot
andcommitted
Improve web chat security UX and bump to 0.4.2
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent c5e5a29 commit 4d8d43b

9 files changed

Lines changed: 136 additions & 29 deletions

File tree

‎README.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ This monorepo contains three projects:
2222

2323
### Prerequisites
2424
- Windows 10 (20H2+) or Windows 11
25-
- .NET 10.0 SDK (preview) - https://dotnet.microsoft.com/download/dotnet/10.0
25+
- .NET 10.0 SDK - https://dotnet.microsoft.com/download/dotnet/10.0
2626
- Windows 10 SDK (for WinUI build) - install via Visual Studio or standalone
2727
- WebView2 Runtime - pre-installed on modern Windows, or get from https://developer.microsoft.com/microsoft-edge/webview2
2828
- PowerToys (optional, for Command Palette extension)
@@ -212,6 +212,8 @@ When Node Mode is enabled in Settings, your Windows PC becomes a **node** that t
212212

213213
> 🔒 **Exec Policy**: `system.run` is gated by an approval policy (saved to `exec-policy.json`). Default rules allow read-only commands (echo, Get-*, hostname, etc.) and deny destructive operations (rm, shutdown, registry edits). Use `system.execApprovals.get/set` to view/modify rules remotely.
214214

215+
> 🔐 **Web Chat secure context**: Remote web chat requires `https://` (or localhost). If using a self-signed cert, trust it in Windows (Trusted Root Certification Authorities) or use an SSH tunnel to localhost.
216+
215217
#### Node Status in Tray Menu
216218

217219
The tray menu shows node connection status:

‎src/OpenClaw.CommandPalette/Pages/OpenClawPage.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public OpenClawPage()
1919
public override IListItem[] GetItems()
2020
{
2121
return [
22-
new ListItem(new OpenUrlCommand("http://localhost:18789"))
22+
new ListItem(new OpenUrlCommand("openclaw://dashboard"))
2323
{
2424
Title = "🦞 Open Dashboard",
2525
Subtitle = "Open OpenClaw web dashboard"

‎src/OpenClaw.Tray.WinUI/App.xaml.cs‎

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ private void OnTrayMenuItemClicked(object? sender, string action)
514514
case "webchat": ShowWebChat(); break;
515515
case "quicksend": ShowQuickSend(); break;
516516
case "history": ShowNotificationHistory(); break;
517-
case "healthcheck": _ = RunHealthCheckAsync(); break;
517+
case "healthcheck": _ = RunHealthCheckAsync(userInitiated: true); break;
518518
case "settings": ShowSettings(); break;
519519
case "autostart": ToggleAutoStart(); break;
520520
case "log": OpenLogFile(); break;
@@ -801,7 +801,7 @@ private void BuildTrayMenu(MenuFlyout flyout)
801801
flyout.Items.Add(historyItem);
802802

803803
var healthCheckItem = new MenuFlyoutItem { Text = "🔄 Run Health Check" };
804-
healthCheckItem.Click += async (s, e) => await RunHealthCheckAsync();
804+
healthCheckItem.Click += async (s, e) => await RunHealthCheckAsync(userInitiated: true);
805805
flyout.Items.Add(healthCheckItem);
806806

807807
flyout.Items.Add(new MenuFlyoutSeparator());
@@ -1106,18 +1106,42 @@ private void StartHealthCheckTimer()
11061106
_ = RunHealthCheckAsync();
11071107
}
11081108

1109-
private async Task RunHealthCheckAsync()
1109+
private async Task RunHealthCheckAsync(bool userInitiated = false)
11101110
{
1111-
if (_gatewayClient == null) return;
1111+
if (_gatewayClient == null)
1112+
{
1113+
if (userInitiated)
1114+
{
1115+
new ToastContentBuilder()
1116+
.AddText("Health Check")
1117+
.AddText("Gateway is not connected yet.")
1118+
.Show();
1119+
}
1120+
return;
1121+
}
11121122

11131123
try
11141124
{
11151125
_lastCheckTime = DateTime.Now;
11161126
await _gatewayClient.CheckHealthAsync();
1127+
if (userInitiated)
1128+
{
1129+
new ToastContentBuilder()
1130+
.AddText("Health Check")
1131+
.AddText("Health check request sent.")
1132+
.Show();
1133+
}
11171134
}
11181135
catch (Exception ex)
11191136
{
11201137
Logger.Warn($"Health check failed: {ex.Message}");
1138+
if (userInitiated)
1139+
{
1140+
new ToastContentBuilder()
1141+
.AddText("Health Check Failed")
1142+
.AddText(ex.Message)
1143+
.Show();
1144+
}
11211145
}
11221146
}
11231147

@@ -1282,11 +1306,18 @@ private void OpenDashboard(string? path = null)
12821306

12831307
var baseUrl = _settings.GatewayUrl
12841308
.Replace("ws://", "http://")
1285-
.Replace("wss://", "https://");
1286-
1287-
var url = string.IsNullOrEmpty(path)
1288-
? $"{baseUrl}?token={Uri.EscapeDataString(_settings.Token)}"
1289-
: $"{baseUrl}/{path}?token={Uri.EscapeDataString(_settings.Token)}";
1309+
.Replace("wss://", "https://")
1310+
.TrimEnd('/');
1311+
1312+
var url = string.IsNullOrEmpty(path)
1313+
? baseUrl
1314+
: $"{baseUrl}/{path.TrimStart('/')}";
1315+
1316+
if (!string.IsNullOrEmpty(_settings.Token))
1317+
{
1318+
var separator = url.Contains('?') ? "&" : "?";
1319+
url = $"{url}{separator}token={Uri.EscapeDataString(_settings.Token)}";
1320+
}
12901321

12911322
try
12921323
{

‎src/OpenClaw.Tray.WinUI/Dialogs/QuickSendDialog.cs‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Microsoft.Toolkit.Uwp.Notifications;
12
using Microsoft.UI.Xaml;
23
using Microsoft.UI.Xaml.Controls;
34
using Microsoft.UI.Xaml.Input;
@@ -124,6 +125,10 @@ private async Task SendMessageAsync()
124125
{
125126
await _client.SendChatMessageAsync(message);
126127
Logger.Info($"Quick send: {message}");
128+
new ToastContentBuilder()
129+
.AddText("Message Sent")
130+
.AddText("Your message was sent to OpenClaw.")
131+
.Show();
127132
Close();
128133
}
129134
catch (Exception ex)

‎src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<EnableMsixTooling>true</EnableMsixTooling>
1010
<ApplicationIcon>Assets\openclaw.ico</ApplicationIcon>
1111
<RootNamespace>OpenClawTray</RootNamespace>
12-
<Version>0.4.1</Version>
12+
<Version>0.4.2</Version>
1313
</PropertyGroup>
1414

1515
<!-- Unpackaged (default): traditional EXE distribution via Inno Setup -->

‎src/OpenClaw.Tray.WinUI/Package.appxmanifest‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<Identity
1313
Name="OpenClaw.Tray"
1414
Publisher="CN=Scott Hanselman, O=Scott Hanselman, L=Forest Grove, S=Oregon, C=US"
15-
Version="0.4.1.0" />
15+
Version="0.4.2.0" />
1616

1717
<Properties>
1818
<DisplayName>OpenClaw Tray</DisplayName>

‎src/OpenClaw.Tray.WinUI/Windows/WebChatWindow.xaml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
<!-- Error display (hidden by default) -->
5353
<ScrollViewer x:Name="ErrorPanel" Grid.Row="1" Visibility="Collapsed" Padding="20">
5454
<StackPanel Spacing="12">
55-
<TextBlock Text="WebView2 Failed to Initialize" FontSize="18" FontWeight="SemiBold"
55+
<TextBlock Text="Web Chat Unavailable" FontSize="18" FontWeight="SemiBold"
5656
Foreground="{ThemeResource SystemFillColorCriticalBrush}"/>
5757
<TextBlock x:Name="ErrorText" TextWrapping="Wrap" IsTextSelectionEnabled="True"
5858
FontFamily="Consolas" FontSize="12"/>

‎src/OpenClaw.Tray.WinUI/Windows/WebChatWindow.xaml.cs‎

Lines changed: 83 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,25 @@ private async Task InitializeWebViewAsync()
9999
e.WebErrorStatus == CoreWebView2WebErrorStatus.ServerUnreachable))
100100
{
101101
Logger.Info("WebChatWindow: Gateway unreachable, showing friendly error");
102-
WebView.Visibility = Visibility.Collapsed;
103-
ErrorPanel.Visibility = Visibility.Visible;
104-
ErrorText.Text = "Can't reach OpenClaw Gateway\n\n" +
102+
ShowErrorMessage("Can't reach OpenClaw Gateway\n\n" +
105103
$"The gateway at {_gatewayUrl} is not responding.\n\n" +
106104
"To connect:\n" +
107105
"• Make sure your OpenClaw gateway is running\n" +
108106
"• If remote, connect via VPN to your home network\n" +
109-
"• Or use SSH tunnel: ssh -N -L 18789:localhost:18789 your-server";
107+
"• Or use SSH tunnel: ssh -N -L 18789:localhost:18789 your-server");
108+
return;
109+
}
110+
111+
if (!e.IsSuccess &&
112+
e.WebErrorStatus.ToString().Contains("Certificate", StringComparison.OrdinalIgnoreCase))
113+
{
114+
Logger.Info("WebChatWindow: TLS certificate issue detected");
115+
ShowErrorMessage(
116+
"The gateway HTTPS certificate is not trusted.\n\n" +
117+
"To connect securely:\n" +
118+
"• Use an HTTPS gateway URL (for example: https://host.tailnet.ts.net)\n" +
119+
"• If self-signed, import the cert into Windows Trusted Root Certification Authorities\n" +
120+
"• Or use SSH tunnel to localhost and keep using localhost URLs");
110121
}
111122
};
112123
WebView.CoreWebView2.NavigationCompleted += _navigationCompletedHandler;
@@ -159,6 +170,59 @@ private async Task InitializeWebViewAsync()
159170

160171
// Set to a test URL to bypass gateway (e.g., "https://www.bing.com"), or null for normal operation
161172
private const string? DEBUG_TEST_URL = null;
173+
174+
private static bool IsLocalHost(Uri uri)
175+
{
176+
return uri.IsLoopback || string.Equals(uri.Host, "localhost", StringComparison.OrdinalIgnoreCase);
177+
}
178+
179+
private bool TryBuildChatUrl(out string url, out string errorMessage)
180+
{
181+
url = string.Empty;
182+
errorMessage = string.Empty;
183+
184+
if (!GatewayUrlHelper.TryNormalizeWebSocketUrl(_gatewayUrl, out var normalizedGatewayUrl) ||
185+
!Uri.TryCreate(normalizedGatewayUrl, UriKind.Absolute, out var gatewayUri))
186+
{
187+
errorMessage = $"Invalid gateway URL: {_gatewayUrl}";
188+
return false;
189+
}
190+
191+
var webScheme = gatewayUri.Scheme.Equals("wss", StringComparison.OrdinalIgnoreCase)
192+
? "https"
193+
: "http";
194+
195+
if (webScheme == "http" && !IsLocalHost(gatewayUri))
196+
{
197+
errorMessage =
198+
"Web chat requires a secure context.\n\n" +
199+
"There is no safe bypass for remote plain HTTP: browsers and WebView enforce this.\n\n" +
200+
"Use one of these options:\n" +
201+
"• Use a trusted HTTPS/WSS endpoint (Let's Encrypt, Tailscale Serve, Caddy)\n" +
202+
"• If self-signed, import your gateway CA/cert into Windows Trusted Root (certmgr.msc)\n" +
203+
"• Or tunnel to localhost: ssh -N -L 18789:localhost:18789 <mac>";
204+
return false;
205+
}
206+
207+
var builder = new UriBuilder(gatewayUri)
208+
{
209+
Scheme = webScheme,
210+
Port = gatewayUri.Port
211+
};
212+
213+
var baseUrl = builder.Uri.GetLeftPart(UriPartial.Authority);
214+
url = $"{baseUrl}?token={Uri.EscapeDataString(_token)}";
215+
return true;
216+
}
217+
218+
private void ShowErrorMessage(string message)
219+
{
220+
LoadingRing.IsActive = false;
221+
LoadingRing.Visibility = Visibility.Collapsed;
222+
WebView.Visibility = Visibility.Collapsed;
223+
ErrorPanel.Visibility = Visibility.Visible;
224+
ErrorText.Text = message;
225+
}
162226

163227
private void NavigateToChat()
164228
{
@@ -172,12 +236,15 @@ private void NavigateToChat()
172236
return;
173237
}
174238

175-
var baseUrl = _gatewayUrl
176-
.Replace("ws://", "http://")
177-
.Replace("wss://", "https://");
178-
179-
var url = $"{baseUrl}?token={Uri.EscapeDataString(_token)}";
180-
Logger.Info($"WebChatWindow: Navigating to {baseUrl} (token hidden)");
239+
if (!TryBuildChatUrl(out var url, out var errorMessage))
240+
{
241+
Logger.Warn($"WebChatWindow: {errorMessage}");
242+
ShowErrorMessage(errorMessage);
243+
return;
244+
}
245+
246+
var safeBaseUrl = url.Split('?')[0];
247+
Logger.Info($"WebChatWindow: Navigating to {safeBaseUrl} (token hidden)");
181248
WebView.CoreWebView2.Navigate(url);
182249
}
183250

@@ -193,10 +260,12 @@ private void OnRefresh(object sender, RoutedEventArgs e)
193260

194261
private void OnPopout(object sender, RoutedEventArgs e)
195262
{
196-
var baseUrl = _gatewayUrl
197-
.Replace("ws://", "http://")
198-
.Replace("wss://", "https://");
199-
var url = $"{baseUrl}?token={Uri.EscapeDataString(_token)}";
263+
if (!TryBuildChatUrl(out var url, out var errorMessage))
264+
{
265+
Logger.Warn($"WebChatWindow: {errorMessage}");
266+
ShowErrorMessage(errorMessage);
267+
return;
268+
}
200269

201270
try
202271
{

‎src/OpenClaw.Tray/OpenClaw.Tray.csproj‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<AssemblyCompany>Scott Hanselman</AssemblyCompany>
1515
<AssemblyProduct>OpenClaw Tray</AssemblyProduct>
1616
<Copyright>Copyright © 2026 Scott Hanselman</Copyright>
17-
<Version>0.4.1</Version>
17+
<Version>0.4.2</Version>
1818
<PublishSingleFile>true</PublishSingleFile>
1919
<SelfContained>true</SelfContained>
2020
<!-- RuntimeIdentifier set at publish time: win-x64 or win-arm64 -->

0 commit comments

Comments
 (0)