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

  1. Change the bind address to 127.0.0.1 (with any port) before calling serve_default.
  2. If external reachability is required, front the loopback-bound health service with a reverse proxy instead of rebinding.
  3. 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

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


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)