tinyhumansai/openhuman · error · anyhow::Error

Refusing to transmit sensitive data over non-HTTPS URL: URL

Error message

Refusing to transmit sensitive data over non-HTTPS URL: URL scheme must be https

What it means

Security guard in the direct Composio tool: ensure_https refuses any URL that does not literally start with 'https://' before a request carrying the x-api-key header is transmitted. It exists so a mis-configured base URL (env var, injected config, hand-built string) can never leak the API key over plaintext HTTP. The only sanctioned exception is loopback HTTP in debug builds (new_with_base_urls_for_loopback / allow_insecure_loopback).

Source

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

// This is opt-in. Users who prefer sovereign/local-only mode skip this entirely.
// The Composio API key is stored in the encrypted secret store.

use crate::openhuman::security::policy::ToolOperation;
use crate::openhuman::security::SecurityPolicy;
use crate::openhuman::tools::traits::{Tool, ToolCategory, ToolResult};
use anyhow::Context;
use async_trait::async_trait;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::sync::Arc;

const COMPOSIO_API_BASE_V2: &str = "https://backend.composio.dev/api/v2";
const COMPOSIO_API_BASE_V3: &str = "https://backend.composio.dev/api/v3";

fn ensure_https(url: &str) -> anyhow::Result<()> {
    if !url.starts_with("https://") {
        anyhow::bail!(
            "Refusing to transmit sensitive data over non-HTTPS URL: URL scheme must be https"
        );
    }
    Ok(())
}

fn is_loopback_http_url(url: &str) -> bool {
    // Parse rather than prefix-match: a raw `starts_with("http://127.0.0.1:")`
    // is fooled by userinfo smuggling like
    // `http://127.0.0.1:8080@evil.com/api/v3/tools`, which reqwest routes to the
    // *parsed* host (`evil.com`). Verify the actual scheme + host and reject any
    // embedded credentials so the insecure-loopback path can never leak the
    // `x-api-key` header to a non-loopback host.
    let Ok(parsed) = url::Url::parse(url) else {
        return false;
    };
    if parsed.scheme() != "http" {
        return false;

View on GitHub (pinned to 7491200858)

Solutions

  1. Use https:// base URLs for any non-loopback host — production pins https://backend.composio.dev/api/v2 and /api/v3
  2. For local mock servers, use the debug-only new_with_base_urls_for_loopback constructor with a 127.0.0.1, ::1, or localhost host
  3. Make sure the scheme string is lowercase 'https://' — the guard is a literal prefix match
  4. Audit any env/config override of Composio base URLs before shipping

Example fix

// before
let base = std::env::var("COMPOSIO_BASE")?; // "http://composio-mirror.internal"

// after — enforce https for non-loopback hosts before constructing the tool
if !base.starts_with("https://") {
    anyhow::bail!("COMPOSIO_BASE must use https (loopback http allowed only via the debug constructor)");
}
Defensive patterns

Strategy: validation

Validate before calling

if !base.starts_with("https://") {
    anyhow::bail!("Composio base URL must be https:// (loopback http only via the debug constructor)");
}

Type guard

fn is_https_url(raw: &str) -> bool {
    url::Url::parse(raw).map(|u| u.scheme() == "https").unwrap_or(false)
}

Try / catch

match tool.list_actions(None).await {
    Ok(items) => { /* ... */ }
    Err(e) if format!("{e:#}").contains("non-HTTPS") => {
        // configuration defect — fail loudly, never downgrade to http
        return Err(e);
    }
    Err(e) => { /* normal API error handling */ }
}

Prevention

When it happens

Trigger: ComposioTool is built with base URLs (or issues a request URL) using http:// — e.g. pointing v2/v3 bases at an http:// staging host in a release build — so ensure_request_url -> ensure_https bails before send(). Also fires for a non-lowercase 'HTTPS://' prefix because the check is a case-sensitive starts_with.

Common situations: Local mock server wired into a release build; base URL injected via config or env with a typo ('http://' instead of 'https://'); a proxy or config rewrite mangling the scheme; hand-concatenated URL strings with wrong casing.

Related errors


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