From 127386d0917eb341b0d4e0f60ed9b9e17dd9ba16 Mon Sep 17 00:00:00 2001 From: Brother Darryl <146fb160a3266e6165bfa385f6048c975eda9e21cf65da097a0b5ea7952532a5@buzz.block.builderlab.xyz> Date: Tue, 11 Aug 2026 12:06:01 -0400 Subject: [PATCH] fix(relay): preserve PR1 loopback sidecar hardening Signed-off-by: Brother Darryl <146fb160a3266e6165bfa385f6048c975eda9e21cf65da097a0b5ea7952532a5@buzz.block.builderlab.xyz> --- crates/buzz-relay/src/main.rs | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/crates/buzz-relay/src/main.rs b/crates/buzz-relay/src/main.rs index f972caaff4..727762429a 100644 --- a/crates/buzz-relay/src/main.rs +++ b/crates/buzz-relay/src/main.rs @@ -1,4 +1,5 @@ use std::collections::{HashMap, HashSet}; +use std::net::{IpAddr, Ipv4Addr}; use std::sync::atomic::Ordering; use std::sync::Arc; @@ -18,7 +19,7 @@ use buzz_db::{Db, DbConfig}; use buzz_pubsub::{rate_limiter::AdmissionRateLimiter, InProcessNip98ReplayGuard, PubSubManager}; use buzz_search::SearchService; -use buzz_relay::config::{Config, MAX_DRAIN_JITTER_MS}; +use buzz_relay::config::{Config, RelayProfile, MAX_DRAIN_JITTER_MS}; use buzz_relay::metrics as relay_metrics; use buzz_relay::router::{build_health_router, build_router}; use buzz_relay::state::{AppBackends, AppState}; @@ -1191,8 +1192,10 @@ async fn run_periodic_until_cancelled( /// ┌─────────────────────────────────────────────────────────┐ /// │ Listener 1: TCP BUZZ_BIND_ADDR:3000 (app router) │ /// │ Listener 2: UDS BUZZ_UDS_PATH (app, optional)│ -/// │ Listener 3: TCP 0.0.0.0:8080 (health only) │ -/// │ Listener 4: TCP 0.0.0.0:9102 (metrics, via │ +/// │ Listener 3: TCP loopback:8080 (single-node health) │ +/// │ or 0.0.0.0:8080 (production health) │ +/// │ Listener 4: TCP loopback:9102 (single-node metrics) │ +/// │ or 0.0.0.0:9102 (production metrics, via │ /// │ PrometheusBuilder — already bound) │ /// │ │ /// │ SIGTERM → shutting_down=true → readiness 503 │ @@ -1217,7 +1220,7 @@ async fn run_single_node(config: Config, tracer_init: telemetry::TracerInit) -> )); } - relay_metrics::install(config.metrics_port, usage_metrics_idle_timeout_secs(60)); + relay_metrics::install_loopback(config.metrics_port, usage_metrics_idle_timeout_secs(60)); let db_path = std::env::var("BUZZ_LOCAL_DB").unwrap_or_else(|_| "buzz-local.sqlite".to_string()); let media_root = @@ -1340,6 +1343,24 @@ async fn run_single_node(config: Config, tracer_init: telemetry::TracerInit) -> /// roughly the 5s grace plus the ack wait. const GRACEFUL_DRAIN_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30); +fn health_listener_ip(profile: RelayProfile) -> IpAddr { + match profile { + RelayProfile::SingleNode => IpAddr::V4(Ipv4Addr::LOCALHOST), + RelayProfile::Production => IpAddr::V4(Ipv4Addr::UNSPECIFIED), + } +} + +#[cfg(test)] +mod listener_tests { + use super::{health_listener_ip, RelayProfile}; + + #[test] + fn single_node_health_listener_is_loopback_only() { + assert!(health_listener_ip(RelayProfile::SingleNode).is_loopback()); + assert!(health_listener_ip(RelayProfile::Production).is_unspecified()); + } +} + async fn serve( router: axum::Router, health_router: axum::Router, @@ -1347,10 +1368,11 @@ async fn serve( ) -> anyhow::Result<()> { let config = &state.config; - let health_listener = tokio::net::TcpListener::bind(("0.0.0.0", config.health_port)) + let health_host = health_listener_ip(config.profile); + let health_listener = tokio::net::TcpListener::bind((health_host, config.health_port)) .await .map_err(|e| anyhow::anyhow!("Failed to bind health port {}: {e}", config.health_port))?; - info!(port = config.health_port, "Health probe listener started"); + info!(host = %health_host, port = config.health_port, "Health probe listener started"); tokio::spawn(async move { axum::serve(health_listener, health_router).await.ok(); });