From 810c8d4d72639470df30aea92f118eae6cad76b2 Mon Sep 17 00:00:00 2001 From: Timmy Date: Mon, 18 May 2026 00:04:32 +0100 Subject: [PATCH] fix: bind project container host ports to 0.0.0.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Story 1130 added HUSKIES_HOST=0.0.0.0 so the server INSIDE a project container binds to all interfaces, but the host-side `docker -p` mapping was still `127.0.0.1:{port}:3001` and `127.0.0.1:{ssh_port}:22` — reachable from the docker host only, blocking remote MCP clients and out-of-host SSH onto the project container. Switch host-side mapping to 0.0.0.0 for both the MCP and SSH ports so project containers spawned via `new project` are reachable from anywhere that can route to the docker host. Existing containers created before this commit retain their localhost-only mapping and need to be recreated to pick up the change. Add a regression test asserting both -p arguments use 0.0.0.0 and reject any 127.0.0.1 restriction in the mapping. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/chat/transport/matrix/new_project.rs | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/server/src/chat/transport/matrix/new_project.rs b/server/src/chat/transport/matrix/new_project.rs index 0ac1810c..89851012 100644 --- a/server/src/chat/transport/matrix/new_project.rs +++ b/server/src/chat/transport/matrix/new_project.rs @@ -913,6 +913,11 @@ pub async fn handle_new_project( /// to all interfaces, making Docker port forwarding reachable from the host. /// Without this the server defaults to `127.0.0.1` inside the container — /// reachable only from within the container itself, not via `docker -p`. +/// +/// Host-side port mappings use `0.0.0.0` so the MCP and SSH endpoints are +/// reachable from outside the docker host (not just from the host itself). +/// `127.0.0.1` had been the conservative default but blocked remote MCP +/// clients and out-of-host SSH onto the project container. fn project_docker_run_args( container_name: &str, port: u16, @@ -927,9 +932,9 @@ fn project_docker_run_args( "--name".into(), container_name.to_string(), "-p".into(), - format!("127.0.0.1:{port}:3001"), + format!("0.0.0.0:{port}:3001"), "-p".into(), - format!("127.0.0.1:{ssh_port}:22"), + format!("0.0.0.0:{ssh_port}:22"), "-e".into(), "HUSKIES_HOST=0.0.0.0".into(), "-e".into(), @@ -1348,6 +1353,39 @@ mod tests { ); } + #[test] + fn project_docker_args_bind_host_ports_to_all_interfaces() { + // Project containers must be reachable from outside the docker host + // (remote MCP clients, ssh into the project container) — host-side + // mapping must use 0.0.0.0, not 127.0.0.1. + let args = project_docker_run_args( + "huskies-myapp", + 3100, + 2200, + "ssh-ed25519 AAAA...", + "Test User", + "test@example.com", + ); + let pairs: Vec<_> = args.windows(2).collect(); + assert!( + pairs + .iter() + .any(|w| w[0] == "-p" && w[1] == "0.0.0.0:3100:3001"), + "expected -p 0.0.0.0:3100:3001 in docker args, got: {args:?}" + ); + assert!( + pairs + .iter() + .any(|w| w[0] == "-p" && w[1] == "0.0.0.0:2200:22"), + "expected -p 0.0.0.0:2200:22 in docker args, got: {args:?}" + ); + assert!( + !pairs.iter().any(|w| w[0] == "-p" + && (w[1].starts_with("127.0.0.1:") || w[1] == "127.0.0.1")), + "host-side mapping must not be 127.0.0.1-restricted: {args:?}" + ); + } + #[test] fn interpret_docker_run_error_missing_image_points_at_script() { let stderr = "Unable to find image 'huskies-project-rust:latest' locally\n\