{"record":{"id":"21ded480298097bc","repo":"Tencent/WeKnora","slug":"url-has-no-hostname","errorCode":null,"errorMessage":"URL has no hostname","messagePattern":"URL has no hostname","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/utils/security.go","lineNumber":1198,"sourceCode":"func ValidateURLForSSRF(rawURL string) error {\n\tif rawURL == \"\" {\n\t\treturn nil // callers that require non-empty should validate separately\n\t}\n\n\t// Normalise: if no scheme, prepend https:// so url.Parse works correctly.\n\tnormalized := rawURL\n\tif !strings.Contains(normalized, \"://\") {\n\t\tnormalized = \"https://\" + normalized\n\t}\n\n\tparsed, err := url.Parse(normalized)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"invalid URL: %w\", err)\n\t}\n\n\thostname := parsed.Hostname()\n\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}","sourceCodeStart":1180,"sourceCodeEnd":1216,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/utils/security.go#L1180-L1216","documentation":"ValidateURLForSSRF requires the parsed URL to carry a hostname. A URL like 'https:///path', 'file://', or a scheme-only string yields an empty parsed.Hostname() and is rejected with 'URL has no hostname'. Without a host the SSRF checks (IP-literal filtering, whitelist lookup) have nothing to evaluate, so the URL cannot be deemed safe.","triggerScenarios":"Calling any storage client constructor or CheckObsConnectivity with a URL missing its host portion — e.g. 'https:///bucket', 'http://:9000', or an endpoint value that after normalization is just a scheme, or was mistyped as 'https:/myhost' (single slash collapses).","commonSituations":"Config where only the port was given, template variables expanding to empty hostnames ('https://${MISSING}/'), typo'd URLs with a single slash after the scheme, or copying 'https://' from a browser address bar with the domain trimmed.","solutions":["Provide the full host in the endpoint: 'https://minio.example.com:9000' instead of 'https://:9000'","Check that config substitution produced a non-empty hostname (echo the resolved value before deployment)","Restore the missing double slash for scheme URLs ('https://host' not 'https:/host')","Add a pre-flight url.Parse + Hostname() check in the caller to fail with a clearer config error"],"exampleFix":"// before\nendpoint := \"https://${OSS_HOST}\" // OSS_HOST unset → https://\n// after\nhost := os.Getenv(\"OSS_HOST\")\nif host == \"\" {\n    return fmt.Errorf(\"OSS_HOST must be set\")\n}\nendpoint := \"https://\" + host","handlingStrategy":"validation","validationCode":"n := endpoint\nif !strings.Contains(n, \"://\") { n = \"https://\" + n }\nu, err := url.Parse(n)\nif err != nil || u.Hostname() == \"\" {\n    return fmt.Errorf(\"endpoint %q has no hostname\", endpoint)\n}","typeGuard":"func hasHostname(raw string) bool {\n    if !strings.Contains(raw, \"://\") { raw = \"https://\" + raw }\n    u, err := url.Parse(raw)\n    return err == nil && u.Hostname() != \"\"\n}","tryCatchPattern":null,"preventionTips":["Always include scheme://host[:port] in endpoint configs — never scheme-only or port-only","Verify templated hostnames (${VAR}) resolve before deploy","Watch for the single-slash typo: https:/host collapses the host","Log the resolved endpoint at startup to catch empty hosts early"],"tags":["ssrf","url-parsing","configuration","object-storage"],"backgroundTag":"url-missing-hostname","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}