zeroclaw-labs/zeroclaw · error · anyhow::Error

Generated image URL must use HTTPS

Error message

Generated image URL must use HTTPS

What it means

parse_public_https_url only accepts https URLs for generated image downloads. After reqwest parses the URL, any scheme other than https (typically http) is rejected. The rule exists because the image URL is fetched server-side with DNS pinning and SSRF checks, and the tool refuses to pull image bytes over a plaintext connection.

Source

Thrown at crates/zeroclaw-tools/src/image_gen.rs:32

const FAL_ERROR_LIMIT_BYTES: usize = 16 * 1024;
const GENERATED_IMAGE_LIMIT_BYTES: usize = 20 * 1024 * 1024;
const MAX_IMAGE_REDIRECTS: usize = 10;

struct ValidatedImageTarget {
    url: reqwest::Url,
    host: String,
    resolved_addrs: Vec<SocketAddr>,
}

fn parse_public_https_url(raw_url: &str) -> anyhow::Result<(reqwest::Url, String, u16)> {
    let raw_url = raw_url.trim();
    if raw_url.is_empty() || raw_url.chars().any(char::is_whitespace) {
        anyhow::bail!("Generated image URL must be a non-empty URL without whitespace");
    }

    let mut url = reqwest::Url::parse(raw_url).context("Invalid generated image URL")?;
    if url.scheme() != "https" {
        anyhow::bail!("Generated image URL must use HTTPS");
    }
    if !url.username().is_empty() || url.password().is_some() {
        anyhow::bail!("Generated image URL userinfo is not allowed");
    }

    let request_host = url
        .host_str()
        .ok_or_else(|| anyhow::Error::msg("Generated image URL must include a host"))?;
    if request_host.ends_with('.') {
        anyhow::bail!("Generated image URL host must not end with a dot");
    }
    let host = domain_guard::normalize_domain(request_host)
        .ok_or_else(|| anyhow::Error::msg("Generated image URL host is invalid"))?;
    let ip_literal = host.parse::<IpAddr>().ok();
    if domain_guard::is_private_or_local_host(&host) {
        anyhow::bail!("Generated image URL targets a local or non-global host");
    }
    if ip_literal.is_some_and(domain_guard::is_cloud_metadata_ip) {

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Serve the image host over HTTPS: put the fal storage or custom domain behind a TLS certificate.
  2. If using fal-managed storage (v3.fal.media), prefer it — it always returns https URLs.
  3. If the same host also serves https, the producer of the URL should emit the https form; the tool itself will not silently upgrade http to https.

Example fix

# before
image url: http://storage.internal.local/files/img.png   # rejected

# after
image url: https://cdn.example.com/files/img.png        # accepted
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

fn is_valid_image_url(u: &str) -> bool { is_non_empty_url(u) && is_https_url(u) }

Try / catch

if let Err(e) = download_generated_image(target).await {
    if e.to_string().contains("must use HTTPS") {
        // storage is emitting http links: fix TLS at the origin, do not downgrade
    }
}

Prevention

When it happens

Trigger: The fal.ai response carries an http:// image URL: a self-hosted fal gateway or custom storage domain without TLS, a storage configuration that emits plain-http links, or a hand-crafted/test URL passed as http://.

Common situations: Self-hosting fal.ai or pointing it at custom object storage (MinIO, internal S3) that is not behind TLS, local development setups where everything is http, and providers that switch schemes after a storage backend change.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/8897050e7c7faa6f. Report an issue: GitHub.