{"record":{"id":"d5bc39e087d69ed2","repo":"Tencent/WeKnora","slug":"base-url-ssrf-validation-failed-w","errorCode":null,"errorMessage":"base_url SSRF validation failed: %w","messagePattern":"base_url SSRF validation failed: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/datasource/httpclient.go","lineNumber":23,"sourceCode":"\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/Tencent/WeKnora/internal/utils\"\n)\n\n// ValidateConnectorBaseURL checks a connector API base URL against the SSRF policy.\n// Empty rawURL is allowed; callers apply their own default before issuing requests.\nfunc ValidateConnectorBaseURL(rawURL string) error {\n\turl := strings.TrimSpace(rawURL)\n\tif url == \"\" {\n\t\treturn nil\n\t}\n\tif !strings.Contains(url, \"://\") {\n\t\turl = \"https://\" + url\n\t}\n\tif err := utils.ValidateURLForSSRF(url); err != nil {\n\t\treturn fmt.Errorf(\"base_url SSRF validation failed: %w\", err)\n\t}\n\treturn nil\n}\n\n// NewConnectorHTTPClient returns an HTTP client with redirect and dial-time SSRF guards.\nfunc NewConnectorHTTPClient(timeout time.Duration) *http.Client {\n\tcfg := utils.DefaultSSRFSafeHTTPClientConfig()\n\tcfg.Timeout = timeout\n\treturn utils.NewSSRFSafeHTTPClient(cfg)\n}\n","sourceCodeStart":5,"sourceCodeEnd":34,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/datasource/httpclient.go#L5-L34","documentation":"ValidateConnectorBaseURL runs utils.ValidateURLForSSRF on the connector's base_url to block requests to loopback/link-local/internal addresses (SSRF protection). This error wraps that underlying failure, so the message includes the specific reason the URL was rejected (private IP, loopback, disallowed scheme, etc.).","triggerScenarios":"Calling any connector init/validation that runs parseFeishuConfig, newClient, parseIMAConfig, or parseYuqueConfig with a base_url pointing at localhost, 127.0.0.1, 169.254.x, or another internal address not on the SSRF whitelist.","commonSituations":"Self-hosted/private deployments where the connector genuinely targets an internal service; typo'd base_url like http://localhost:3000; Docker environments where internal hostnames resolve to blocked ranges.","solutions":["Use the public HTTPS endpoint URL for the service instead of localhost/internal addresses.","If the deployment is intentionally private, add the hostname to SSRF_WHITELIST per the validation policy.","Read the wrapped %w cause to see exactly which rule failed (scheme, IP range, port) and correct that aspect of the URL."],"exampleFix":"// before\n\"base_url\": \"http://127.0.0.1:8080\"\n// after\n\"base_url\": \"https://open.example-service.com\"  // or add host to SSRF_WHITELIST","handlingStrategy":"validation","validationCode":"u, _ := url.Parse(baseURL)\nip := net.ParseIP(u.Hostname())\nif ip != nil && (ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast()) {\n    return errors.New(\"base_url points at a private/loopback address\")\n}","typeGuard":"func isPublicHTTPS(u *url.URL) bool {\n    return (u.Scheme == \"https\" || u.Scheme == \"http\") &&\n        net.ParseIP(u.Hostname()) == nil // not a raw IP\n}","tryCatchPattern":"if err := datasource.ValidateConnectorBaseURL(baseURL); err != nil {\n    if strings.Contains(err.Error(), \"SSRF\") {\n        // guide user to use a public URL or SSRF_WHITELIST\n    }\n    return err\n}","preventionTips":["Use official public HTTPS endpoints for connectors.","For private deployments, register hosts in SSRF_WHITELIST up front.","Pre-validate base_url at config entry, not at first request.","Read the wrapped cause to identify the exact SSRF rule triggered."],"tags":["security","ssrf","url-validation"],"backgroundTag":"ssrf-url-blocked","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}