mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
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>
This commit is contained in:
parent
8356aa4d21
commit
1ac082d075
@@ -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"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user