{"record":{"id":"d940dc49dba9cf43","repo":"Tencent/WeKnora","slug":"ssrf-validation-failed-s","errorCode":null,"errorMessage":"SSRF validation failed: %s","messagePattern":"SSRF validation failed: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/utils/security.go","lineNumber":1215,"sourceCode":"\tif hostname == \"\" {\n\t\treturn fmt.Errorf(\"URL has no hostname\")\n\t}\n\n\t// A whitelist relaxes host/IP restrictions only. It must never turn other\n\t// schemes (file://, gopher://, etc.) into valid outbound request targets.\n\tscheme := strings.ToLower(parsed.Scheme)\n\tif scheme != \"http\" && scheme != \"https\" {\n\t\treturn fmt.Errorf(\"invalid scheme: %s (only http/https allowed)\", scheme)\n\t}\n\n\t// If the host is whitelisted, skip the heavy checks.\n\tif IsSSRFWhitelisted(hostname) {\n\t\treturn nil\n\t}\n\n\t// Delegate to the full SSRF validation (uses the normalised URL).\n\tif safe, reason := isSSRFSafeURL(normalized); !safe {\n\t\treturn fmt.Errorf(\"SSRF validation failed: %s\", reason)\n\t}\n\treturn nil\n}\n\n// IsSystemProxy 判断是否为系统代理\nfunc IsSystemProxy(host string) bool {\n\tproxyCfg := httpproxy.FromEnvironment()\n\tfor _, proxyUrl := range []string{\n\t\tproxyCfg.HTTPProxy,\n\t\tproxyCfg.HTTPSProxy,\n\t} {\n\t\tif proxyUrl == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif parse, err := url.Parse(proxyUrl); err == nil {\n\t\t\tif parse.Host == host {\n\t\t\t\treturn true\n\t\t\t}","sourceCodeStart":1197,"sourceCodeEnd":1233,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/utils/security.go#L1197-L1233","documentation":"ValidateURLForSSRF delegates to isSSRFSafeURL for the deep checks (private/link-local IP literals, DNS resolving to internal addresses, localhost, metadata endpoints, etc.). When isSSRFSafeURL reports the URL is unsafe, the reason string is surfaced as 'SSRF validation failed: %s'. This is the catch-all SSRF rejection — the specific reason (e.g. 'resolves to private IP', 'loopback address') is embedded in the message.","triggerScenarios":"The normalized URL passes scheme/hostname checks and is not whitelisted, but isSSRFSafeURL flags it: hostname is localhost/127.0.0.1, resolves to RFC1918 or link-local space, points at the cloud metadata IP (169.254.169.254), or resolves via DNS to an internal address. Called from any storage client constructor or CheckObsConnectivity.","commonSituations":"Misconfigured endpoints pointing at internal infrastructure from an environment where that is disallowed; tests using 127.0.0.1 against a local MinIO without adding it to the SSRF whitelist; SSRF probes where a user-supplied URL targets internal services.","solutions":["Read the embedded reason in the message to see which check fired (loopback, private range, metadata IP)","Add the host or its CIDR to the SSRF whitelist config (ValidateSSRFWhitelistEntries-managed list) if the target is legitimately required — whitelisting skips the heavy checks","Use the public hostname instead of a raw internal IP so DNS-based policy can evaluate it correctly","For local development of MinIO/OBS mocks, whitelist 'localhost' explicitly in the dev environment config","If it is an SSRF attempt, block the request and log the source"],"exampleFix":"// before\nclient, err := newMinioClient(\"http://127.0.0.1:9000\", ...) // blocked: loopback\n// after\n// dev config: add localhost to whitelist\nSSRF_WHITELIST=localhost,127.0.0.0/8\nclient, err := newMinioClient(\"http://127.0.0.1:9000\", ...)","handlingStrategy":"try-catch","validationCode":"n := endpoint\nif !strings.Contains(n, \"://\") { n = \"https://\" + n }\nu, _ := url.Parse(n)\nips, err := net.LookupIP(u.Hostname())\nif err == nil {\n    for _, ip := range ips {\n        if ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() {\n            log.Printf(\"endpoint %s resolves to internal IP %s; whitelist it or change target\", u.Hostname(), ip)\n        }\n    }\n}","typeGuard":"func isPublicEndpoint(raw string) bool {\n    if !strings.Contains(raw, \"://\") { raw = \"https://\" + raw }\n    u, err := url.Parse(raw)\n    if err != nil { return false }\n    ips, err := net.LookupIP(u.Hostname())\n    if err != nil || len(ips) == 0 { return false }\n    for _, ip := range ips {\n        if ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() { return false }\n    }\n    return true\n}","tryCatchPattern":"if err := ValidateURLForSSRF(target); err != nil {\n    var ssrfErr = err\n    log.Printf(\"SSRF block for %q: %v\", target, ssrfErr) // reason is embedded in the message\n    return fmt.Errorf(\"outbound request to %q blocked: %w\", target, err)\n}","preventionTips":["Whitelist legitimate internal hosts (e.g. localhost for local MinIO) via the SSRF whitelist config","Use public hostnames rather than raw internal IPs where policy allows DNS evaluation","Never pass user-supplied URLs straight into storage client constructors","Read the embedded reason string to identify which check (loopback/private/metadata) fired"],"tags":["ssrf","security","internal-network","url-validation"],"backgroundTag":"ssrf-validation-failed","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}