From 1ac082d075530507f447c25e532ec9cfa686c032 Mon Sep 17 00:00:00 2001 From: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@sprout-oss.stage.blox.sqprod.co> Date: Sat, 27 Jun 2026 21:39:57 -0400 Subject: [PATCH] Preserve local relay host for desktop HTTP calls Co-authored-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@sprout-oss.stage.blox.sqprod.co> Signed-off-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@sprout-oss.stage.blox.sqprod.co> --- desktop/src-tauri/src/relay.rs | 40 +++++++++++----------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/desktop/src-tauri/src/relay.rs b/desktop/src-tauri/src/relay.rs index 425ea835d..a0fb1c4bd 100644 --- a/desktop/src-tauri/src/relay.rs +++ b/desktop/src-tauri/src/relay.rs @@ -75,30 +75,16 @@ pub fn relay_http_base_url(relay_url: &str) -> String { let trimmed = relay_url.trim().trim_end_matches('/'); if let Some(suffix) = trimmed.strip_prefix("wss://") { - return format!("https://{}", normalize_loopback_host(suffix)); + return format!("https://{}", suffix); } if let Some(suffix) = trimmed.strip_prefix("ws://") { - return format!("http://{}", normalize_loopback_host(suffix)); + return format!("http://{}", suffix); } trimmed.to_string() } -/// Rewrite an exact `localhost` host to `127.0.0.1`, preserving port and path. -/// -/// macOS resolves `localhost` to both `::1` and `127.0.0.1`; reqwest's -/// happy-eyeballs may try `::1` first, which fails when the relay binds IPv4 -/// only (`0.0.0.0`). Forcing the IPv4 literal makes every HTTP write connect on -/// the first attempt. Exact-match only — `localhost.evil.com` is untouched. -fn normalize_loopback_host(authority: &str) -> String { - let host_len = authority.find([':', '/']).unwrap_or(authority.len()); - if &authority[..host_len] == "localhost" { - return format!("127.0.0.1{}", &authority[host_len..]); - } - authority.to_string() -} - pub fn relay_api_base_url() -> String { if let Some(base) = configured_env_var("BUZZ_RELAY_HTTP") { return base.trim_end_matches('/').to_string(); @@ -669,21 +655,24 @@ mod tests { ); } - // ── relay_http_base_url loopback normalization ─────────────────────────── + // ── relay_http_base_url scheme conversion ──────────────────────────────── #[test] - fn loopback_ws_localhost_rewritten_to_ipv4() { + fn loopback_ws_localhost_preserves_authority() { + // Tenant host-binding keys off the HTTP Host/authority. The desktop must + // not rewrite localhost to 127.0.0.1, or local dev HTTP calls target a + // different unmapped community than the WebSocket URL. assert_eq!( relay_http_base_url("ws://localhost:3000"), - "http://127.0.0.1:3000" + "http://localhost:3000" ); } #[test] - fn loopback_trailing_slash_rewritten_to_ipv4() { + fn loopback_trailing_slash_removed_authority_preserved() { assert_eq!( relay_http_base_url("ws://localhost:3000/"), - "http://127.0.0.1:3000" + "http://localhost:3000" ); } @@ -704,9 +693,7 @@ mod tests { } #[test] - fn localhost_substring_host_not_rewritten() { - // Exact-match only: a host that merely starts with "localhost" must NOT - // be rewritten, or a malicious host could hijack the loopback path. + fn localhost_substring_host_unchanged() { assert_eq!( relay_http_base_url("ws://localhost.evil.com:3000"), "http://localhost.evil.com:3000" @@ -714,11 +701,10 @@ mod tests { } #[test] - fn loopback_wss_localhost_rewritten_to_ipv4() { - // The wss:// dev arm normalizes identically to ws://. + fn loopback_wss_localhost_preserves_authority() { assert_eq!( relay_http_base_url("wss://localhost:3000"), - "https://127.0.0.1:3000" + "https://localhost:3000" ); }