zeroclaw-labs/zeroclaw · error · anyhow::Error
{field_name} must use https://, got {url}
Error message
{field_name} must use https://, got {url} What it means
https_base_url validates the WeChat channel's api_base_url and cdn_base_url during WeChatChannel::new: after trimming whitespace and trailing '/', the URL must start with https:// or construction fails, naming the field and the offending value. Defaults (DEFAULT_API_BASE_URL / CDN_BASE_URL) are https, so this only fires on explicitly configured values. The rule exists because the channel sends/decrypts authenticated media and API traffic that must not traverse plaintext HTTP.
Source
Thrown at crates/zeroclaw-channels/src/wechat.rs:438
anyhow::Error::msg("media nested hex aes_key must be 16 bytes")
});
}
anyhow::bail!(
"media aes_key must decode to 16 raw bytes or 32 hex chars, got {} bytes",
decoded.len()
)
}
fn https_base_url(
field_name: &str,
value: Option<String>,
default: &str,
) -> anyhow::Result<String> {
let url = value.unwrap_or_else(|| default.to_string());
let url = url.trim().trim_end_matches('/').to_string();
if !url.starts_with("https://") {
anyhow::bail!("{field_name} must use https://, got {url}");
}
Ok(url)
}
/// Interpret an iLink `sendmessage` response body, returning a description
/// of the failure when the API reported one.
///
/// The iLink API reports send failures as HTTP 200 with a non-zero
/// `ret`/`errcode` in the JSON body — the same envelope the getUpdates
/// sync loop parses. Checking only the HTTP status treats those failures
/// (e.g. an expired or missing `context_token`) as success, so the message
/// is silently dropped.
///
/// An empty or non-JSON 2xx body carries no envelope to inspect and is
/// treated as success, preserving the pre-check behavior for those shapes.
fn sendmessage_body_error(body: &str) -> Option<String> {
if body.trim().is_empty() {
return None;View on GitHub (pinned to 88bb9c8533)
Solutions
- Change the configured api_base_url / cdn_base_url to start with https:// (keep the rest of the path identical).
- If the origin is plaintext, put an HTTPS reverse proxy (nginx/Caddy with TLS) in front and point the channel at the https proxy URL.
- For local testing, use a locally trusted cert (mkcert) so the URL is still https://.
- Or simply omit both keys to fall back to the built-in https defaults.
Example fix
# before [channels.wechat.bot] api_base_url = "http://ilink.internal:8080" # -> "api_base_url must use https://, got http://ilink.internal:8080" # after — terminate TLS upstream and use https [channels.wechat.bot] api_base_url = "https://ilink.internal"
Defensive patterns
Strategy: validation
Validate before calling
// Reject non-https overrides before constructing the channel.
fn ensure_https(field: &str, url: Option<&str>) -> anyhow::Result<()> {
if let Some(u) = url {
let t = u.trim().trim_end_matches('/');
anyhow::ensure!(t.starts_with("https://"), "{field} must use https://, got {t}");
}
Ok(())
}
ensure_https("api_base_url", cfg.api_base_url.as_deref())?;
ensure_https("cdn_base_url", cfg.cdn_base_url.as_deref())?; Try / catch
match WeChatChannel::new(/* ... */).await {
Ok(ch) => { /* use channel */ }
Err(e) if e.to_string().contains("must use https://") => {
// Config defect caught at construction: fix the URL or front the origin
// with an HTTPS proxy, then restart. No retry path exists.
}
Err(e) => return Err(e),
} Prevention
- Lint config in CI: every *_base_url under [channels.wechat.*] must start with https://.
- For local mocks, use mkcert/TLS proxies so test configs keep the https scheme.
- Prefer omitting api_base_url/cdn_base_url unless you truly run a private endpoint — defaults are https.
- Watch for the single-slash typo 'https:/' — it fails the check too.
When it happens
Trigger: WeChatChannel::new with config [channels.wechat.<alias>] api_base_url or cdn_base_url set to http://ilink.example.com, http://localhost:8080, a ws:// value, or a URL with a typo like https:/ (single slash). Also fires when someone points the channel at a local mock/proxy over plain http for testing.
Common situations: Local development against a self-hosted iLink mock over http; a private deployment that terminated TLS on an upstream proxy but configured the internal http:// address; migrating a config from an environment where http was tolerated.
Related errors
- Refusing to transmit sensitive data over non-HTTPS URL: URL
- Refusing to transmit sensitive data over non-HTTPS URL: URL
- refusing non-HTTPS attachment URL: {url}
- WeChat QR code fetch failed ({$status}): {$body}
- use_tls is true but broker_url uses 'mqtt://' (not 'mqtts://
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/ef47f0c49201e920.
Report an issue: GitHub.