zeroclaw-labs/zeroclaw · error
unsupported URL scheme: {}
Error message
unsupported URL scheme: {} What it means
browser_delegate validates every URL it touches — both explicit task URLs and URLs extracted from free-form task text by a regex — through reqwest::Url parsing followed by a scheme allowlist: only http and https pass. Any other scheme (ftp, file, ws, chrome, javascript, ...) is rejected before the browser CLI subprocess sees it.
Source
Thrown at crates/zeroclaw-tools/src/browser_delegate.rs:90
fn validate_url(&self, url: &str) -> anyhow::Result<()> {
let parsed = url.parse::<reqwest::Url>().map_err(|e| {
::zeroclaw_log::record!(
WARN,
::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)
.with_outcome(::zeroclaw_log::EventOutcome::Failure)
.with_attrs(::serde_json::json!({
"url": url,
"error": format!("{}", e),
})),
"browser_delegate: invalid URL"
);
anyhow::Error::msg(format!("invalid URL '{}': {}", url, e))
})?;
// Only allow http/https schemes
let scheme = parsed.scheme();
if scheme != "http" && scheme != "https" {
anyhow::bail!("unsupported URL scheme: {}", scheme);
}
let domain = parsed.host_str().unwrap_or("").to_string();
if domain.is_empty() {
anyhow::bail!("URL has no host: {}", url);
}
// Check blocked domains first (deny takes precedence)
for blocked in &self.config.blocked_domains {
if domain_matches(&domain, blocked) {
anyhow::bail!("domain '{}' is blocked by browser_delegate policy", domain);
}
}
// If allowed_domains is non-empty, it acts as an allowlist
if !self.config.allowed_domains.is_empty() {
let allowed = selfView on GitHub (pinned to 88bb9c8533)
Solutions
- Remove or rewrite non-http(s) links in the task text (use https:// mirrors)
- If another scheme is genuinely needed, handle it outside browser_delegate — the allowlist is a security boundary
- Pre-scan task text with the same https?:// extraction and reject before submitting
Example fix
// before task = "Download ftp://mirror.example.org/pkg.tar.gz" // after task = "Download https://mirror.example.org/pkg.tar.gz"
Defensive patterns
Strategy: validation
Validate before calling
for m in regex::Regex::new(r"https?://[^\s\)\]\},\"'`<>")
.unwrap()
.find_iter(&task)
{
let parsed: reqwest::Url = m.as_str().parse()?;
if parsed.scheme() != "http" && parsed.scheme() != "https" {
return Err(format!("non-http(s) URL in task: {}", m.as_str()));
}
} Try / catch
match delegate.execute(args).await {
Ok(res) if res.success => { /* ... */ }
Ok(res) => {
if res.error.as_deref().unwrap_or_default().contains("unsupported URL scheme") {
// strip or rewrite the offending link from the task text
}
}
Err(e) => return Err(e),
} Prevention
- Sanitize free-form task text for URLs before delegation
- Keep http/https as the only schemes in agent-facing documentation
- Test tasks containing mixed links before enabling browser_delegate
When it happens
Trigger: A task containing `ftp://mirror.example.com/file` or `file:///etc/passwd`. Note that validate_task_urls scans the entire prompt with a https?:// regex, so a scheme-violating link buried in prose also triggers this check.
Common situations: Agents pasting README links that mix ftp/file schemes into the delegated task; users expecting desktop-browser scheme tolerance; test fixtures using about:blank or chrome:// URLs.
Related errors
- Custom model_provider `{prefix}:<url>` requires a URL beginn
- OpenAI Codex endpoint override must use http:// or https://
- URL has no host: {}
- Only http:// or https:// URLs are allowed
- Only http:// and https:// URLs are allowed
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/3c010f8061bc1a79.
Report an issue: GitHub.