{"record":{"id":"8897050e7c7faa6f","repo":"zeroclaw-labs/zeroclaw","slug":"generated-image-url-must-use-https","errorCode":null,"errorMessage":"Generated image URL must use HTTPS","messagePattern":"Generated image URL must use HTTPS","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/image_gen.rs","lineNumber":32,"sourceCode":"const FAL_ERROR_LIMIT_BYTES: usize = 16 * 1024;\nconst GENERATED_IMAGE_LIMIT_BYTES: usize = 20 * 1024 * 1024;\nconst MAX_IMAGE_REDIRECTS: usize = 10;\n\nstruct ValidatedImageTarget {\n    url: reqwest::Url,\n    host: String,\n    resolved_addrs: Vec<SocketAddr>,\n}\n\nfn parse_public_https_url(raw_url: &str) -> anyhow::Result<(reqwest::Url, String, u16)> {\n    let raw_url = raw_url.trim();\n    if raw_url.is_empty() || raw_url.chars().any(char::is_whitespace) {\n        anyhow::bail!(\"Generated image URL must be a non-empty URL without whitespace\");\n    }\n\n    let mut url = reqwest::Url::parse(raw_url).context(\"Invalid generated image URL\")?;\n    if url.scheme() != \"https\" {\n        anyhow::bail!(\"Generated image URL must use HTTPS\");\n    }\n    if !url.username().is_empty() || url.password().is_some() {\n        anyhow::bail!(\"Generated image URL userinfo is not allowed\");\n    }\n\n    let request_host = url\n        .host_str()\n        .ok_or_else(|| anyhow::Error::msg(\"Generated image URL must include a host\"))?;\n    if request_host.ends_with('.') {\n        anyhow::bail!(\"Generated image URL host must not end with a dot\");\n    }\n    let host = domain_guard::normalize_domain(request_host)\n        .ok_or_else(|| anyhow::Error::msg(\"Generated image URL host is invalid\"))?;\n    let ip_literal = host.parse::<IpAddr>().ok();\n    if domain_guard::is_private_or_local_host(&host) {\n        anyhow::bail!(\"Generated image URL targets a local or non-global host\");\n    }\n    if ip_literal.is_some_and(domain_guard::is_cloud_metadata_ip) {","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/image_gen.rs#L14-L50","documentation":"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.","triggerScenarios":"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://.","commonSituations":"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.","solutions":["Serve the image host over HTTPS: put the fal storage or custom domain behind a TLS certificate.","If using fal-managed storage (v3.fal.media), prefer it — it always returns https URLs.","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."],"exampleFix":"# before\nimage url: http://storage.internal.local/files/img.png   # rejected\n\n# after\nimage url: https://cdn.example.com/files/img.png        # accepted","handlingStrategy":"validation","validationCode":"fn is_https_url(raw: &str) -> bool {\n    reqwest::Url::parse(raw.trim()).map(|u| u.scheme() == \"https\").unwrap_or(false)\n}","typeGuard":"fn is_valid_image_url(u: &str) -> bool { is_non_empty_url(u) && is_https_url(u) }","tryCatchPattern":"if let Err(e) = download_generated_image(target).await {\n    if e.to_string().contains(\"must use HTTPS\") {\n        // storage is emitting http links: fix TLS at the origin, do not downgrade\n    }\n}","preventionTips":["Put custom storage/gateways behind TLS certificates so every emitted link is https.","Assert https on provider URLs in integration tests to catch scheme regressions early."],"tags":["url","https","tls","image-gen","ssrf-guard","rust"],"backgroundTag":"insecure-scheme-rejected","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}