tinyhumansai/openhuman · error · anyhow::Error

debug Composio base URL must be HTTPS or loopback HTTP

Error message

debug Composio base URL must be HTTPS or loopback HTTP

What it means

Constructor validation in the debug-only ComposioTool::new_with_base_urls_for_loopback: each of base_v2/base_v3 must either start with https:// or be an http URL whose parsed host is loopback (localhost, 127.0.0.0/8, ::1) with no embedded userinfo. Host checking is done by parsing (is_loopback_http_base), not prefix matching, specifically so userinfo smuggling like http://127.0.0.1:8080@evil.com cannot route the x-api-key header to evil.com.

Source

Thrown at src/openhuman/integrations/composio/tools/direct.rs:110

    pub(crate) fn auth_key_fingerprint(&self) -> u64 {
        crate::openhuman::integrations::composio::direct_auth::fingerprint_api_key(&self.api_key)
    }

    /// Debug-test seam for raw integration coverage: construct a direct
    /// Composio tool against explicit v2/v3 base URLs. Non-HTTPS URLs are
    /// accepted only for loopback hosts and only in debug builds.
    #[cfg(debug_assertions)]
    pub fn new_with_base_urls_for_loopback(
        api_key: &str,
        default_entity_id: Option<&str>,
        security: Arc<SecurityPolicy>,
        base_v2: String,
        base_v3: String,
    ) -> anyhow::Result<Self> {
        for base in [&base_v2, &base_v3] {
            if !base.starts_with("https://") && !is_loopback_http_base(base) {
                anyhow::bail!("debug Composio base URL must be HTTPS or loopback HTTP");
            }
        }
        Ok(Self::new_internal(
            api_key,
            default_entity_id,
            security,
            base_v2,
            base_v3,
            true,
        ))
    }

    /// Test-only seam: construct with an explicit Composio v3 base URL so
    /// unit tests can point the direct `/tools` request — including the
    /// `tags` filter — at a local mock instead of `backend.composio.dev`.
    ///
    /// `#[cfg(test)]`-gated on purpose: `list_tool_schemas_v3` attaches the
    /// `x-api-key` header to whatever `base_v3` holds, so the only way to

View on GitHub (pinned to 7491200858)

Solutions

  1. Point the base at a loopback host: http://127.0.0.1:<port>, http://localhost:<port>, or http://[::1]:<port>, with no userinfo
  2. Or serve the mock over https and keep any host you like
  3. Remember the constructor only exists under cfg(debug_assertions) — release builds must use ComposioTool::new with the pinned HTTPS endpoints

Example fix

// before
let tool = ComposioTool::new_with_base_urls_for_loopback(key, None, sec,
    "http://10.0.0.5:8080/api/v2".into(), "http://10.0.0.5:8080/api/v3".into())?;

// after — loopback mock in a debug build
let tool = ComposioTool::new_with_base_urls_for_loopback(key, None, sec,
    "http://127.0.0.1:8080/api/v2".into(), "http://127.0.0.1:8080/api/v3".into())?;
Defensive patterns

Strategy: validation

Validate before calling

#[cfg(debug_assertions)]
{
    // Both bases must be https OR loopback http (127.0.0.1 / localhost / [::1], no userinfo)
    for base in [&base_v2, &base_v3] {
        assert!(base.starts_with("https://") || is_loopback_http_base(base),
            "base {base} is neither https nor loopback");
    }
}

Type guard

fn is_safe_debug_base(raw: &str) -> bool {
    if raw.starts_with("https://") { return true; }
    let Ok(u) = url::Url::parse(&format!("{}/", raw.trim_end_matches('/'))) else { return false };
    u.scheme() == "http"
        && u.username().is_empty()
        && u.password().is_none()
        && matches!(u.host(),
            Some(url::Host::Domain(h)) if h.eq_ignore_ascii_case("localhost")
            || matches!(u.host(), Some(url::Host::Ipv4(ip)) if ip.is_loopback())
            || matches!(u.host(), Some(url::Host::Ipv6(ip)) if ip.is_loopback()))
}

Try / catch

match ComposioTool::new_with_base_urls_for_loopback(key, None, sec, v2, v3) {
    Ok(tool) => tool,
    Err(e) if format!("{e:#}").contains("HTTPS or loopback") => {
        // test harness misconfig — bind the mock to 127.0.0.1 or serve TLS
        panic!("test base URL rejected: {e:#}");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: A debug build constructs the tool with a base such as http://staging.composio.dev/... or http://192.168.1.10:8080 (LAN host, not loopback), or a loopback-looking URL containing user:pass@ — is_loopback_http_base returns false and the constructor bails.

Common situations: Pointing tests at a mock on a LAN IP or docker host (host.docker.internal, 172.17.x.x) instead of a loopback address; reusing a staging http URL in a dev build; copy-pasting a proxy URL with credentials.

Related errors


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