siyuan-note/siyuan · error
URL must start with http:// or https://
Error message
URL must start with http:// or https://
What it means
resolvePublicTarget normalizes the outbound request target for the SSRF-safe dialer: it validates the URL scheme and host. Only http (port 80) and https (port 443) are supported; any other scheme is rejected before any connection is made, because the safe dialer can only reason about plain TCP targets.
Solutions
- Prefix the target with https:// (or http:// explicitly)
- Validate the scheme in the calling code before invoking the agent request tool
- Never feed file://, ftp://, or ws:// URLs to the HTTP request path — use the appropriate subsystem instead
- Sanitize model-generated URLs to guarantee a valid absolute http(s) URL
Example fix
// before url := "example.com/api" // after url := "https://example.com/api"
Defensive patterns
Strategy: validation
Validate before calling
u, err := url.Parse(target)
if err != nil || (u.Scheme != "http" && u.Scheme != "https") {
return fmt.Errorf("target must be an absolute http(s) URL: %q", target)
} Try / catch
if strings.Contains(err.Error(), "must start with http:// or https://") {
// normalize the URL with a scheme and retry once
} Prevention
- Always build absolute URLs with an explicit https:// scheme
- Sanitize model-generated URLs before passing them to fetch tools
- Reject relative paths and custom schemes at the input boundary
When it happens
Trigger: RoundTrip receives a request whose URL scheme is not http/https — e.g. ws://, ftp://, file://, or a URL without any scheme — passed to the SSRF-safe HTTP client used by agent tools.
Common situations: An AI agent constructing a URL from user/injected input with a missing or exotic scheme; a developer passing a relative path or custom scheme to the fetch helper; template-built URLs losing the https:// prefix.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- data?.msg || data?.message ||…
- access to private/internal IP is prohibited
- access to private/internal IP is prohibited
- CalDAV: calendar object path is invalid
- CalDAV: calendar path is invalid
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/2c4003f237009bdc.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/util/httprequest.go:153
conn.Close()
return nil, err
}
resp.Request = req
resp.Body = newConnectionReadCloser(req.Context(), resp.Body, conn)
return resp, nil
}
func (t *ssrfSafeTransport) resolvePublicTarget(ctx context.Context, targetURL *url.URL) (string, error) {
host := targetURL.Hostname()
port := targetURL.Port()
if port == "" {
switch targetURL.Scheme {
case "http":
port = "80"
case "https":
port = "443"
default:
return "", errors.New("URL must start with http:// or https://")
}
}
if ip := net.ParseIP(host); ip != nil {
if isPrivateIP(ip) {
return "", errors.New("access to private/internal IP is prohibited")
}
return net.JoinHostPort(ip.String(), port), nil
}
ips, err := t.lookupIPAddr(ctx, host)
if err != nil {
return "", errors.New("failed to resolve host: " + err.Error())
}
if len(ips) == 0 {
return "", errors.New("host has no IP address: " + host)
}
for _, ipAddr := range ips {View on GitHub (pinned to 9f775e8a12)