{"record":{"id":"7ab6d642fe613bfa","repo":"unicity-aos/aos-ce","slug":"unicity-aos-health-service-must-bind-to-127-0-0-1","errorCode":null,"errorMessage":"Unicity AOS health service must bind to 127.0.0.1","messagePattern":"Unicity AOS health service must bind to 127\\.0\\.0\\.1","errorType":"validation","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"crates/unicity-aos-bootstrap/src/health.rs","lineNumber":117,"sourceCode":"    if readiness.ready().await {\n        (StatusCode::OK, Json(HealthBody { ready: true })).into_response()\n    } else {\n        // The same body covers unavailable, denied, malformed, timed-out, and\n        // genuinely unready runtime states. No runtime diagnostic crosses HTTP.\n        (\n            StatusCode::SERVICE_UNAVAILABLE,\n            Json(HealthBody { ready: false }),\n        )\n            .into_response()\n    }\n}\n\n/// Reject every address except literal IPv4 loopback.\npub fn validate_bind_address(address: SocketAddr) -> std::io::Result<()> {\n    if address.ip() == IpAddr::V4(LOOPBACK_ADDR) {\n        Ok(())\n    } else {\n        Err(std::io::Error::new(\n            std::io::ErrorKind::InvalidInput,\n            \"Unicity AOS health service must bind to 127.0.0.1\",\n        ))\n    }\n}\n\n/// Run the product health service on the fixed loopback endpoint.\n///\n/// # Errors\n/// Returns an error when the loopback listener cannot be created or the server\n/// cannot run.\npub async fn serve_default() -> std::io::Result<()> {\n    let address = SocketAddr::from((LOOPBACK_ADDR, HEALTH_PORT));\n    validate_bind_address(address)?;\n    let listener = TcpListener::bind(address).await?;\n    axum::serve(listener, router(AstridRuntimeReadiness)).await\n}\n","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/unicity-aos/aos-ce/blob/f6f22024fb1e8d122f28a1b4a9f75aee448ae839/crates/unicity-aos-bootstrap/src/health.rs#L99-L135","documentation":"validate_bind_address only permits the literal IPv4 loopback address 127.0.0.1 for the Unicity AOS health service and rejects every other SocketAddr with an InvalidInput io error. This hardens the health endpoint so it can never be exposed on a network-accessible interface.","triggerScenarios":"Calling serve_default (which calls validate_bind_address) with a SocketAddr whose IP is anything other than 127.0.0.1 — e.g. 0.0.0.0, ::1, 127.0.0.2, or a LAN address.","commonSituations":"Passing 0.0.0.0 or the hostname-resolved address to make the health service reachable in a container; using IPv6 loopback ::1 instead of IPv4 127.0.0.1; reading a bind address from config that defaults to a public interface.","solutions":["Change the bind address to 127.0.0.1 (with any port) before calling serve_default.","If external reachability is required, front the loopback-bound health service with a reverse proxy instead of rebinding.","Normalize configured addresses in code: parse the host and substitute 127.0.0.1 for the health service regardless of config."],"exampleFix":"// before\nlet addr: SocketAddr = \"0.0.0.0:8081\".parse()?;\nserve_default(addr).await?;\n// after\nlet addr: SocketAddr = \"127.0.0.1:8081\".parse()?;\nserve_default(addr).await?;","handlingStrategy":"validation","validationCode":"fn assert_loopback(addr: SocketAddr) -> Result<(), String> {\n    if addr.ip() == IpAddr::V4(std::net::Ipv4Addr::LOCALHOST) { Ok(()) }\n    else { Err(format!(\"{} is not 127.0.0.1\", addr)) }\n}","typeGuard":"fn is_ipv4_loopback(addr: SocketAddr) -> bool {\n    addr.ip() == IpAddr::V4(std::net::Ipv4Addr::LOCALHOST)\n}","tryCatchPattern":"if let Err(e) = validate_bind_address(addr) {\n    eprintln!(\"refusing to start health service: {e}; use 127.0.0.1\");\n    std::process::exit(2);\n}","preventionTips":["Hardcode the health bind address as 127.0.0.1 rather than reading it from config.","Never accept 0.0.0.0 or hostnames for loopback-only services; front with a proxy if external access is needed.","Remember ::1 (IPv6 loopback) is also rejected — use IPv4 literal 127.0.0.1."],"tags":["security","network","bind-address","rust"],"backgroundTag":"invalid-argument-value","analyzedSha":"f6f22024fb1e8d122f28a1b4a9f75aee448ae839","analyzedAt":"2026-09-13T03:04:44.565Z","contentChangedAt":"2026-09-13T03:04:44.565Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}