unicity-aos/aos-ce · error · std::io::Error
Unicity AOS health service must bind to 127.0.0.1
Error message
Unicity AOS health service must bind to 127.0.0.1
What it means
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.
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.
Example fix
// before let addr: SocketAddr = "0.0.0.0:8081".parse()?; serve_default(addr).await?; // after let addr: SocketAddr = "127.0.0.1:8081".parse()?; serve_default(addr).await?;
Defensive patterns
Strategy: validation
Validate before calling
fn assert_loopback(addr: SocketAddr) -> Result<(), String> {
if addr.ip() == IpAddr::V4(std::net::Ipv4Addr::LOCALHOST) { Ok(()) }
else { Err(format!("{} is not 127.0.0.1", addr)) }
} Type guard
fn is_ipv4_loopback(addr: SocketAddr) -> bool {
addr.ip() == IpAddr::V4(std::net::Ipv4Addr::LOCALHOST)
} Try / catch
if let Err(e) = validate_bind_address(addr) {
eprintln!("refusing to start health service: {e}; use 127.0.0.1");
std::process::exit(2);
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- product manifest path must be a regular file
- temporary product manifest path must be a regular file
- AOS capsule directory must be a real directory
- hook-adapter-oracle: dropping mismatched context reply on
- canonical document exceeds bound
AI-assisted analysis of unicity-aos/aos-ce@f6f22024fb (2026-09-13).
Data as JSON: /api/errors/7ab6d642fe613bfa.
Report an issue: GitHub.
Appendix: source
Thrown at crates/unicity-aos-bootstrap/src/health.rs:117
if readiness.ready().await {
(StatusCode::OK, Json(HealthBody { ready: true })).into_response()
} else {
// The same body covers unavailable, denied, malformed, timed-out, and
// genuinely unready runtime states. No runtime diagnostic crosses HTTP.
(
StatusCode::SERVICE_UNAVAILABLE,
Json(HealthBody { ready: false }),
)
.into_response()
}
}
/// Reject every address except literal IPv4 loopback.
pub fn validate_bind_address(address: SocketAddr) -> std::io::Result<()> {
if address.ip() == IpAddr::V4(LOOPBACK_ADDR) {
Ok(())
} else {
Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Unicity AOS health service must bind to 127.0.0.1",
))
}
}
/// Run the product health service on the fixed loopback endpoint.
///
/// # Errors
/// Returns an error when the loopback listener cannot be created or the server
/// cannot run.
pub async fn serve_default() -> std::io::Result<()> {
let address = SocketAddr::from((LOOPBACK_ADDR, HEALTH_PORT));
validate_bind_address(address)?;
let listener = TcpListener::bind(address).await?;
axum::serve(listener, router(AstridRuntimeReadiness)).await
}
View on GitHub (pinned to f6f22024fb)