From 5a4a63ad2aa160b1572f4326f32d4ac444f62202 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Sat, 27 Jun 2026 12:15:17 +0800 Subject: [PATCH] fix(artifacts): route signed downloads through active bridge --- ...pp_controller_desktop_runtime_helpers.dart | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/app/app_controller_desktop_runtime_helpers.dart b/lib/app/app_controller_desktop_runtime_helpers.dart index fb66cb11..ab8ba3c3 100644 --- a/lib/app/app_controller_desktop_runtime_helpers.dart +++ b/lib/app/app_controller_desktop_runtime_helpers.dart @@ -1163,19 +1163,34 @@ extension AppControllerDesktopRuntimeHelpers on AppController { if (rawDownloadUrl.isEmpty) { return const _ArtifactBytesResult.skipped(); } - final uri = Uri.tryParse(rawDownloadUrl); + var uri = Uri.tryParse(rawDownloadUrl); if (uri == null || (uri.scheme != 'http' && uri.scheme != 'https')) { return const _ArtifactBytesResult.skipped(); } final bridgeEndpoint = resolveBridgeAcpEndpointInternal(); final bridgeHost = bridgeEndpoint?.host.trim().toLowerCase() ?? ''; - final downloadHost = uri.host.trim().toLowerCase(); + var downloadHost = uri.host.trim().toLowerCase(); final isLoopback = downloadHost == '127.0.0.1' || downloadHost == 'localhost' || downloadHost == '::1'; - final sameBridgeHost = + var sameBridgeHost = bridgeEndpoint != null && (downloadHost == bridgeHost || isLoopback); + // A local/self-hosted bridge can decorate artifacts with its configured + // public URL. Keep the signed path/query, but download through the bridge + // endpoint the user actually selected so credentials never go cross-host. + if (!sameBridgeHost && + bridgeEndpoint != null && + uri.path == '/artifacts/openclaw/download' && + uri.queryParameters['sig']?.trim().isNotEmpty == true) { + uri = uri.replace( + scheme: bridgeEndpoint.scheme, + host: bridgeEndpoint.host, + port: bridgeEndpoint.hasPort ? bridgeEndpoint.port : null, + ); + downloadHost = uri.host.trim().toLowerCase(); + sameBridgeHost = downloadHost == bridgeHost; + } if (!sameBridgeHost) { return const _ArtifactBytesResult.skipped(); }