tinyhumansai/openhuman · critical

refusing to bind on non-loopback address

Error message

refusing to bind on non-loopback address {resolved_host} without an explicit operator-supplied RPC token ({})

What it means

The embedded HTTP RPC server refuses to bind a non-loopback host unless an explicit operator-supplied token (OPENHUMAN_CORE_TOKEN) is present. The auto-generated {workspace}/core.token is by design insufficient: remote clients cannot read a workspace-local file, so it would leave a LAN or public endpoint effectively unauthenticated. A log line and a highlighted [SECURITY] stderr banner both precede the bail, naming the env var and the loopback alternative.

Solutions

  1. Set OPENHUMAN_CORE_TOKEN to a strong random value in the core's environment and have clients send it as the bearer token.
  2. Or keep the default loopback bind and reach the core through an authenticated tunnel or the in-process embedded path.
  3. Never rely on {workspace}/core.token to secure a non-loopback endpoint.

Example fix

# before
OPENHUMAN_CORE_HOST=0.0.0.0 ./openhuman-core serve   # refuses to bind

# after
export OPENHUMAN_CORE_TOKEN="$(openssl rand -hex 32)"
OPENHUMAN_CORE_HOST=0.0.0.0 ./openhuman-core serve
Defensive patterns

Strategy: validation

Validate before calling

let host = resolved_bind_host();
if !host.is_loopback() && std::env::var("OPENHUMAN_CORE_TOKEN").is_err() {
    anyhow::bail!("refusing non-loopback bind: set OPENHUMAN_CORE_TOKEN or bind loopback");
}

Prevention

When it happens

Trigger: Config or env sets the bind host to 0.0.0.0 or a LAN IP - e.g. external-core debugging, docker/cloud deployment, or LAN access for the iOS client - while OPENHUMAN_CORE_TOKEN is unset in the core's environment.

Common situations: Operators assuming the per-workspace core.token file secures remote binds; docker deployments binding 0.0.0.0 by habit; exposing the core on the LAN without planning auth.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17). Data as JSON: /api/errors/faf4ff52bec6da38. Report an issue: GitHub.

Appendix: source

Thrown at src/core/runtime/builder.rs:692

        // so treating it as "explicit" would be fail-open.
        if crate::openhuman::security::pairing::is_public_bind(&resolved_host)
            && !self.has_operator_token
        {
            log::error!(
                "[core] SECURITY: refusing to bind on public address {resolved_host} without an \
                 explicit operator-supplied RPC token. Set {} in your environment (or hand the \
                 bearer in-memory via the embedded core handle) to secure the RPC endpoint.",
                crate::core::auth::CORE_TOKEN_ENV_VAR
            );
            eprintln!(
                "\n\x1b[1;31m[SECURITY]\x1b[0m Refusing to bind on {resolved_host} without {}.\n\
                 The auto-generated {{workspace}}/core.token does NOT secure a public bind —\n\
                 remote clients cannot read it. Set {} in your environment to secure the\n\
                 RPC endpoint, or bind on a loopback address.\n",
                crate::core::auth::CORE_TOKEN_ENV_VAR,
                crate::core::auth::CORE_TOKEN_ENV_VAR
            );
            anyhow::bail!(
                "refusing to bind on non-loopback address {resolved_host} without an explicit \
                 operator-supplied RPC token ({})",
                crate::core::auth::CORE_TOKEN_ENV_VAR
            );
        }

        let preferred_port = resolved_port;
        let host = resolved_host;
        let pick = crate::openhuman::platform::connectivity::rpc::pick_listen_port_for_host(
            host.as_str(),
            preferred_port,
        )
        .await
        .map_err(|err| {
            log::error!("[core] Failed to bind to {host}:{preferred_port}: {err}");
            anyhow::Error::new(err)
        })?;
        let listen_port = pick.port;

View on GitHub (pinned to 7491200858)