{"record":{"id":"0a6360b8e47bb498","repo":"zeroclaw-labs/zeroclaw","slug":"refusing-to-transmit-sensitive-data-over-non-https","errorCode":null,"errorMessage":"Refusing to transmit sensitive data over non-HTTPS URL: URL scheme must be https","messagePattern":"Refusing to transmit sensitive data over non-HTTPS URL: URL scheme must be https","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/qq.rs","lineNumber":72,"sourceCode":"    voice_dedup_parts: Vec<QQVoiceDedupPart>,\n}\n\n/// Response from QQ media upload API.\n#[derive(Debug, Deserialize)]\nstruct QQUploadResponse {\n    file_info: String,\n    ttl: Option<u64>,\n}\n\n/// Cached upload entry to avoid re-uploading the same file within TTL.\nstruct UploadCacheEntry {\n    file_info: String,\n    expires_at: u64,\n}\n\nfn ensure_https(url: &str) -> anyhow::Result<()> {\n    if !url.starts_with(\"https://\") {\n        anyhow::bail!(\n            \"Refusing to transmit sensitive data over non-HTTPS URL: URL scheme must be https\"\n        );\n    }\n    Ok(())\n}\n\n/// Check whether a file extension is a natively supported QQ voice format.\nfn is_native_voice_ext(ext: &str) -> bool {\n    matches!(ext.to_ascii_lowercase().as_str(), \"wav\" | \"mp3\" | \"silk\")\n}\n\nfn has_supported_transcription_extension(filename: &str) -> bool {\n    let ext = Path::new(filename)\n        .extension()\n        .and_then(|e| e.to_str())\n        .unwrap_or(\"\")\n        .to_ascii_lowercase();\n","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/qq.rs#L54-L90","documentation":"ensure_https is the guard run by upload_media, send_media_message, and send_text_markdown before any QQ HTTP call; it rejects every URL that does not start with \"https://\" because these requests carry the app access token and user media. It exists so a misconfigured or tampered base/media URL can never downgrade credentials to plaintext HTTP. It is a deliberate refusal, not a network failure.","triggerScenarios":"Any URL reaching the QQ HTTP helpers configured or overridden as http:// — e.g. an api base override for testing, a proxy rewriting URLs, or a media URL from a marker like [IMAGE:http://...] where the value is used as an endpoint rather than a fetch target.","commonSituations":"Pointing the channel at an http:// mock server in tests (use an https test server instead); corporate proxies that hand back http:// URLs; copy-pasting an internal http endpoint into a field that is used for authenticated API calls.","solutions":["Change the offending URL to https:// (real QQ endpoints api.sgroup.qq.com and bots.qq.com are https)","For local tests, use a TLS-capable mock server or the platform's https test endpoint instead of plain http","Audit any api-base/media URL override fields in [channels.qq.*] and remove http:// values"],"exampleFix":"# before\n[channels.qq.main]\napi_base = \"http://qq-mock.internal:8080\"   # -> bail: non-HTTPS URL refused\n\n# after\n[channels.qq.main]\napi_base = \"https://qq-mock.internal:8443\"","handlingStrategy":"validation","validationCode":"// Before invoking QQ send paths, apply the same rule the library enforces:\nfn qq_urls_are_https(urls: &[&str]) -> anyhow::Result<()> {\n    for u in urls {\n        if !u.starts_with(\"https://\") {\n            anyhow::bail!(\"refusing non-HTTPS QQ URL: {u}\");\n        }\n    }\n    Ok(())\n}\nqq_urls_are_https(&[api_base, media_url])?;","typeGuard":"fn is_https(url: &str) -> bool {\n    url.starts_with(\"https://\")\n}\n\nassert!(is_https(\"https://api.sgroup.qq.com\"));\nassert!(!is_https(\"http://qq-mock.internal:8080\"));","tryCatchPattern":"match ch.send(&msg).await {\n    Err(e) if e.to_string().contains(\"non-HTTPS URL\") => {\n        // Deterministic security refusal: fix the URL source (config override / proxy); never downgrade.\n    }\n    rest => rest?,\n}","preventionTips":["Never put http:// endpoints in [channels.qq.*] URL fields — the guard exists to protect the access token","Use https mock servers in integration tests instead of plain-http stubs","Audit URL-producing overrides after proxy or environment changes"],"tags":["qq","https","security","tls","url-validation"],"backgroundTag":"insecure-url-rejected","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}