{"record":{"id":"aac5a80ec65debff","repo":"zeroclaw-labs/zeroclaw","slug":"refusing-non-https-attachment-url-url","errorCode":null,"errorMessage":"refusing non-HTTPS attachment URL: {url}","messagePattern":"refusing non-HTTPS attachment URL: (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/wechat.rs","lineNumber":1173,"sourceCode":"            .and_then(|value| value.split(';').next())\n            .and_then(mime_guess::get_mime_extensions_str)\n            .and_then(|exts: &[&str]| exts.first().copied())\n            .unwrap_or(kind.default_extension());\n\n        format!(\n            \"wechat_attachment_{}.{}\",\n            uuid::Uuid::new_v4().simple(),\n            ext\n        )\n    }\n\n    async fn download_remote_attachment(\n        &self,\n        url: &str,\n        kind: WeChatAttachmentKind,\n    ) -> anyhow::Result<WeChatMediaPayload> {\n        if !url.starts_with(\"https://\") {\n            anyhow::bail!(\"refusing non-HTTPS attachment URL: {url}\");\n        }\n        let resp = self\n            .client\n            .get(url)\n            .timeout(API_TIMEOUT)\n            .send()\n            .await\n            .with_context(|| format!(\"attachment download failed: {url}\"))?;\n\n        if !resp.status().is_success() {\n            let status = resp.status();\n            let body = resp.text().await.unwrap_or_default();\n            anyhow::bail!(\"attachment download failed ({status}): {body}\");\n        }\n\n        if let Some(len) = resp.content_length()\n            && len > WECHAT_MEDIA_MAX_BYTES\n        {","sourceCodeStart":1155,"sourceCodeEnd":1191,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/wechat.rs#L1155-L1191","documentation":"`download_remote_attachment` fetches remote WeChat attachments but hard-refuses any URL whose scheme is not `https://`. The check runs before any network I/O, so this is a policy rejection, not a network failure. It exists to guarantee that file content and the AES-encrypted upload credentials never travel over plaintext HTTP.","triggerScenarios":"Passing a WeChat attachment target starting with `http://` (or any other non-`https://` scheme that `is_remote_url` classifies as remote) to a send call, which routes to `download_remote_attachment` via `load_attachment_payload`. Examples: `http://cdn.example.com/file.png`, `http://localhost:8000/report.pdf`, `ftp://...` style remote targets.","commonSituations":"Local development servers expose files over plain HTTP (`http://localhost:8000`) and get wired into attachment targets; internal tooling emits `http://` intranet CDN links; a typo or lowercased scheme variant; third-party feeds that still return http links; forwarding emails/chat messages whose embedded media links are http.","solutions":["Serve the file over HTTPS — put a TLS terminator (Caddy, nginx, or a tunnel like ngrok/localtunnel) in front of the HTTP service and use the `https://` URL.","Fix the upstream producer of the URL so it emits `https://` links.","If the content must come from a plain-HTTP internal host, download it yourself into `workspace_dir` and attach it as a local file path instead of a URL."],"exampleFix":"# before\nattachment.target = \"http://localhost:8000/report.pdf\"  # -> refusing non-HTTPS attachment URL\n\n# after (option 1: TLS in front)\nattachment.target = \"https://files.example.com/report.pdf\"\n\n# after (option 2: local file inside workspace)\ncurl -o /workspace/report.pdf http://localhost:8000/report.pdf\nattachment.target = \"report.pdf\"","handlingStrategy":"validation","validationCode":"// enforce the scheme at the source, before building the attachment\nfn https_only(url: &str) -> anyhow::Result<&str> {\n    if url.starts_with(\"https://\") {\n        Ok(url)\n    } else {\n        anyhow::bail!(\"attachment URL must use https: {url}\")\n    }\n}\nlet target = https_only(&attachment_url)?;","typeGuard":"fn is_https_attachment_url(url: &str) -> bool {\n    url.starts_with(\"https://\")\n}","tryCatchPattern":"match channel.send(msg_with_attachment(url)).await {\n    Err(err) if err.to_string().contains(\"refusing non-HTTPS attachment URL\") => {\n        // download via your own TLS path into the workspace, then attach locally\n        let local = mirror_into_workspace(&url).await?; // your https-capable fetcher\n        channel.send(msg_with_attachment(&local)).await?;\n    }\n    other => other?,\n}","preventionTips":["Validate `target.starts_with(\"https://\")` wherever attachment URLs enter your system (user input, scraped HTML, feeds).","For local dev servers, front them with a TLS proxy or tunnel so emitted links are already https.","Do not try to smuggle http URLs past the check by scheme trickery; the channel refuses them before any request by design."],"tags":["security","https","url","attachment","wechat","download"],"backgroundTag":"insecure-protocol-rejected","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}