{"record":{"id":"2c4003f237009bdc","repo":"siyuan-note/siyuan","slug":"url-must-start-with-http-or-https","errorCode":null,"errorMessage":"URL must start with http:// or https://","messagePattern":"URL must start with http:// or https://","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/httprequest.go","lineNumber":153,"sourceCode":"\t\tconn.Close()\n\t\treturn nil, err\n\t}\n\tresp.Request = req\n\tresp.Body = newConnectionReadCloser(req.Context(), resp.Body, conn)\n\treturn resp, nil\n}\n\nfunc (t *ssrfSafeTransport) resolvePublicTarget(ctx context.Context, targetURL *url.URL) (string, error) {\n\thost := targetURL.Hostname()\n\tport := targetURL.Port()\n\tif port == \"\" {\n\t\tswitch targetURL.Scheme {\n\t\tcase \"http\":\n\t\t\tport = \"80\"\n\t\tcase \"https\":\n\t\t\tport = \"443\"\n\t\tdefault:\n\t\t\treturn \"\", errors.New(\"URL must start with http:// or https://\")\n\t\t}\n\t}\n\n\tif ip := net.ParseIP(host); ip != nil {\n\t\tif isPrivateIP(ip) {\n\t\t\treturn \"\", errors.New(\"access to private/internal IP is prohibited\")\n\t\t}\n\t\treturn net.JoinHostPort(ip.String(), port), nil\n\t}\n\n\tips, err := t.lookupIPAddr(ctx, host)\n\tif err != nil {\n\t\treturn \"\", errors.New(\"failed to resolve host: \" + err.Error())\n\t}\n\tif len(ips) == 0 {\n\t\treturn \"\", errors.New(\"host has no IP address: \" + host)\n\t}\n\tfor _, ipAddr := range ips {","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/util/httprequest.go#L135-L171","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nurl := \"example.com/api\"\n// after\nurl := \"https://example.com/api\"","handlingStrategy":"validation","validationCode":"u, err := url.Parse(target)\nif err != nil || (u.Scheme != \"http\" && u.Scheme != \"https\") {\n    return fmt.Errorf(\"target must be an absolute http(s) URL: %q\", target)\n}","typeGuard":null,"tryCatchPattern":"if strings.Contains(err.Error(), \"must start with http:// or https://\") {\n    // normalize the URL with a scheme and retry once\n}","preventionTips":["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"],"tags":["network","url","validation","ssrf"],"backgroundTag":"invalid-url-format","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}