{"record":{"id":"6767e829c6f6bb62","repo":"MHSanaei/3x-ui","slug":"unsupported-url-scheme-q","errorCode":null,"errorMessage":"unsupported URL scheme %q","messagePattern":"unsupported URL scheme %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/web/service/url_safety.go","lineNumber":26,"sourceCode":"\t\"strings\"\n\t\"time\"\n\n\t\"github.com/mhsanaei/3x-ui/v3/internal/util/netsafe\"\n)\n\n// SanitizeHTTPURL validates and normalizes an http(s) URL without resolving\n// DNS. Use SanitizePublicHTTPURL at the point of an outbound request.\nfunc SanitizeHTTPURL(raw string) (string, error) {\n\traw = strings.TrimSpace(raw)\n\tif raw == \"\" {\n\t\treturn \"\", nil\n\t}\n\tu, err := url.Parse(raw)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif u.Scheme != \"http\" && u.Scheme != \"https\" {\n\t\treturn \"\", fmt.Errorf(\"unsupported URL scheme %q\", u.Scheme)\n\t}\n\tif u.Host == \"\" || u.Hostname() == \"\" {\n\t\treturn \"\", fmt.Errorf(\"URL host is required\")\n\t}\n\tclean := &url.URL{\n\t\tScheme:   u.Scheme,\n\t\tHost:     u.Host,\n\t\tPath:     u.Path,\n\t\tRawPath:  u.RawPath,\n\t\tRawQuery: u.RawQuery,\n\t\tFragment: u.Fragment,\n\t}\n\treturn clean.String(), nil\n}\n\n// SanitizePublicHTTPURL validates and normalizes an http(s) URL, then blocks\n// private/internal targets unless the caller explicitly allows them.\nfunc SanitizePublicHTTPURL(raw string, allowPrivate bool) (string, error) {","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/MHSanaei/3x-ui/blob/ad32144c42455696ea9f14e12168beac3e25f5d2/internal/web/service/url_safety.go#L8-L44","documentation":"SanitizeHTTPURL rejects any URL whose scheme is not exactly http or https after url.Parse. This is the first gate of the panel's SSRF-hardened URL intake (used for things like URL-test/health-check endpoints): non-HTTP schemes are refused before any DNS or request happens, because schemes like file://, gopher://, or unix-socket-style URLs have no safe outbound semantics here.","triggerScenarios":"Passing 'ftp://host/file', 'file:///etc/passwd', 'javascript:...', a scheme with wrong case is fine (url.Parse lowercases), but 'HTTPS://x' with embedded whitespace or a URL like 'example.com/path' (no scheme at all, so u.Scheme == \"\" and it fails here rather than at the host check) will trigger it. Note: a schemeless input does NOT hit this branch only if it parses with empty scheme — empty scheme is also 'unsupported'.","commonSituations":"Users pasting a bare domain into a field that expects a full URL; config imports carrying legacy gopher/socks URLs; typo'd schemes like 'http//host'.","solutions":["Prefix the input with http:// or https:// before storing it (most common fix for user-entered hosts).","Validate the field at the UI/API layer with a scheme allowlist so the user gets feedback before save.","If the target legitimately needs another scheme, it does not belong in an HTTP-checked field — route it through a different, purpose-built config path."],"exampleFix":"// before\nraw := \"example.com/health\"\nclean, err := service.SanitizeHTTPURL(raw) // unsupported URL scheme \"\"\n\n// after\nraw := \"example.com/health\"\nif !strings.Contains(raw, \"://\") {\n    raw = \"https://\" + raw\n}\nclean, err := service.SanitizeHTTPURL(raw)","handlingStrategy":"validation","validationCode":"// Enforce scheme before storing/using a URL\nu, err := url.Parse(strings.TrimSpace(raw))\nif err != nil || (u.Scheme != \"http\" && u.Scheme != \"https\") || u.Host == \"\" {\n    if !strings.Contains(raw, \"://\") && raw != \"\" {\n        raw = \"https://\" + raw // auto-upgrade bare hosts\n    }\n}","typeGuard":"func isHTTPURL(raw string) bool {\n    u, err := url.Parse(strings.TrimSpace(raw))\n    return err == nil && (u.Scheme == \"http\" || u.Scheme == \"https\") && u.Host != \"\"\n}","tryCatchPattern":"clean, err := service.SanitizeHTTPURL(raw)\nif err != nil {\n    if strings.Contains(err.Error(), \"unsupported URL scheme\") {\n        // fix input to http(s), never broaden the allowlist\n    }\n}","preventionTips":["Collect URLs from users through a field that documents 'full https:// URL required'.","Reject non-http schemes at the API boundary with a 400, not deep in outbound code."],"tags":["url","ssrf","validation"],"backgroundTag":null,"analyzedSha":"ad32144c42455696ea9f14e12168beac3e25f5d2","analyzedAt":"2026-08-15T11:13:23.905Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}