@@ -205,6 +205,10 @@ async fn main() -> Result<()> {
205205
206206 // Register this VMM instance for local discovery
207207 discovery:: cleanup_stale_registrations ( ) ;
208+ // whether the management API binds a TCP address reachable beyond the local
209+ // host (i.e. not a Unix socket and not a loopback IP). Used to warn when the
210+ // surface is exposed without authentication.
211+ let mut listen_tcp_public = false ;
208212 let listen_address = {
209213 // Use Rocket's Endpoint type to parse the address exactly as Rocket would,
210214 // then override the port with the figment's port value (matching Rocket's behavior).
@@ -213,6 +217,7 @@ async fn main() -> Result<()> {
213217 match endpoint. tcp ( ) {
214218 Some ( addr) => {
215219 let port: u16 = figment. extract_inner ( "port" ) . unwrap_or ( addr. port ( ) ) ;
220+ listen_tcp_public = !addr. ip ( ) . is_loopback ( ) ;
216221 format ! ( "{}:{port}" , addr. ip( ) )
217222 }
218223 None => endpoint. to_string ( ) ,
@@ -241,6 +246,14 @@ async fn main() -> Result<()> {
241246 if config. auth . enabled && !config. auth . htpasswd_file . as_os_str ( ) . is_empty ( ) {
242247 api_auth = api_auth. with_htpasswd_file ( & config. auth . htpasswd_file ) ?;
243248 }
249+ if !config. auth . enabled && listen_tcp_public {
250+ warn ! (
251+ "the management API is bound to a non-loopback address ({listen_address}) with \
252+ `[auth] enabled = false`: the entire VMM control surface (create/stop VM, UI, \
253+ pRPC) is exposed WITHOUT authentication. set `[auth] enabled = true` with a \
254+ token, or bind `address` to localhost / a Unix socket."
255+ ) ;
256+ }
244257 let supervisor = {
245258 let cfg = & config. supervisor ;
246259 let abs_exe = Path :: new ( & cfg. exe ) . absolutize ( ) ?;
0 commit comments