From 3fc43cc3115d010b8d78977e0f69593ca66f6161 Mon Sep 17 00:00:00 2001 From: Zachery Aaron Shores-Chmielewski Date: Fri, 28 Aug 2026 02:09:38 +0400 Subject: [PATCH] fix(dashboard): bind the server to loopback Restrict the local dashboard listener to 127.0.0.1 instead of exposing it on every interface, with a listener-address regression test. --- crates/dashboard/src/server.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/crates/dashboard/src/server.rs b/crates/dashboard/src/server.rs index 0c5e97f..6252770 100644 --- a/crates/dashboard/src/server.rs +++ b/crates/dashboard/src/server.rs @@ -25,12 +25,16 @@ pub(crate) struct AppState { pub shutdown_notify: Arc, } +async fn bind_listener(port: u16) -> std::io::Result { + tokio::net::TcpListener::bind(("127.0.0.1", port)).await +} + pub(crate) async fn run_server(state: AppState, port: u16) { run_server_with_routes(state, port, Router::new()).await; } pub(crate) async fn run_server_with_routes(state: AppState, port: u16, extra: Router) { - let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{port}")) + let listener = bind_listener(port) .await .expect("failed to bind HTTP server"); let shutdown = Arc::clone(&state.shutdown_notify); @@ -342,6 +346,13 @@ mod tests { } } + #[tokio::test] + async fn dashboard_listener_is_loopback_only() { + let listener = bind_listener(0).await.expect("bind dashboard listener"); + let address = listener.local_addr().expect("read dashboard address"); + assert!(address.ip().is_loopback(), "dashboard bound to {address}"); + } + #[test] fn registered_plugin_page_appears_in_shared_navigation() { let state = state_with_plugin(PluginPage::new(