{"record":{"id":"0916fef3fd177c43","repo":"MHSanaei/3x-ui","slug":"bad-ss-host-port","errorCode":null,"errorMessage":"bad ss host:port","messagePattern":"bad ss host:port","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/util/link/outbound.go","lineNumber":365,"sourceCode":"\t}\n\tcore := strings.TrimPrefix(link, \"ss://\")\n\tat := strings.Index(core, \"@\")\n\tif at >= 0 {\n\t\t// modern\n\t\tuserB64 := core[:at]\n\t\thp := strings.TrimRight(core[at+1:], \"/\")\n\t\tuserInfo, err := base64DecodeFlexible(userB64)\n\t\tif err != nil {\n\t\t\t// SIP022 (2022-blake3-*) userinfo is percent-encoded, not base64.\n\t\t\tif dec, uerr := url.QueryUnescape(userB64); uerr == nil {\n\t\t\t\tuserInfo = dec\n\t\t\t} else {\n\t\t\t\tuserInfo = userB64 // not b64, rare\n\t\t\t}\n\t\t}\n\t\tcolon := strings.LastIndex(hp, \":\")\n\t\tif colon < 0 {\n\t\t\treturn nil, fmt.Errorf(\"bad ss host:port\")\n\t\t}\n\t\thost := hp[:colon]\n\t\tport, err := strconv.Atoi(hp[colon+1:])\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"bad ss port %q: %w\", hp[colon+1:], err)\n\t\t}\n\t\tmethod, pass := splitMethodPass(userInfo)\n\t\tidentity := \"ss:\" + method + \":\" + pass + \"@\" + host + \":\" + strconv.Itoa(port)\n\t\tob := Outbound{\n\t\t\t\"protocol\": \"shadowsocks\",\n\t\t\t\"tag\":      remark,\n\t\t\t\"settings\": map[string]any{\n\t\t\t\t\"servers\": []any{\n\t\t\t\t\tmap[string]any{\"address\": host, \"port\": port, \"password\": pass, \"method\": method},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\treturn &ParseResult{Outbound: ob, Identity: identity}, nil","sourceCodeStart":347,"sourceCodeEnd":383,"githubUrl":"https://github.com/MHSanaei/3x-ui/blob/ad32144c42455696ea9f14e12168beac3e25f5d2/internal/util/link/outbound.go#L347-L383","documentation":"Returned by parseShadowsocks for a SIP002-form link (ss://base64userinfo@host:port) when, after taking the part after the last '@' and trimming trailing '/', there is no ':' at all — the parser cannot split host from port. Hostname-only or IPv6-without-brackets links are the usual culprits, since the code splits on the LAST colon and expects 'host:port' text.","triggerScenarios":"Link truncated before the port ('ss://xxx@host'); IPv6 host written without square brackets followed by port, or with brackets but no ':port' after; a remark fragment swallowing the port (ss://xxx@host#remark where '/' trimming already failed); plain host with no port.","commonSituations":"Hand-typed or sed-edited ss links; providers emitting 'ss://method:pass@host' with the port dropped; IPv6 servers exported by clients that omit brackets in the userinfo form.","solutions":["Inspect the link after '@': it must look like host:443 (or [v6host]:443).","Wrap IPv6 hosts in square brackets and always include an explicit port.","Re-export the link from the source client's share function.","For batch imports, validate with a regexp like ^ss://[^@]+@[^@]+:\\d+ before parsing."],"exampleFix":"// before\nss://YWVzLTI1Ni1nY206cGFzcw==@example.com\n\n// after\nss://YWVzLTI1Ni1nY206cGFzcw==@example.com:8388","handlingStrategy":"validation","validationCode":"var sip002Re = regexp.MustCompile(`^ss://[^@]+@([a-zA-Z0-9.\\-]+|\\[[^]]+\\]):\\d{1,5}`)\nfunc looksLikeValidSIP002(link string) bool { return sip002Re.MatchString(strings.TrimSpace(link)) }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always include an explicit numeric port in ss links.","Bracket IPv6 hosts: [2001:db8::1]:443.","Validate with a regexp before bulk import."],"tags":["parsing","proxy-links","shadowsocks","sip002","validation"],"backgroundTag":null,"analyzedSha":"ad32144c42455696ea9f14e12168beac3e25f5d2","analyzedAt":"2026-08-15T11:13:23.905Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}