{"record":{"id":"1c4a21686f6807ec","repo":"MHSanaei/3x-ui","slug":"not-trojan","errorCode":null,"errorMessage":"not trojan","messagePattern":"not trojan","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/util/link/outbound.go","lineNumber":300,"sourceCode":"\t\t\"settings\": map[string]any{\n\t\t\t\"address\":    host,\n\t\t\t\"port\":       port,\n\t\t\t\"id\":         id,\n\t\t\t\"flow\":       params.Get(\"flow\"),\n\t\t\t\"encryption\": firstNonEmpty(params.Get(\"encryption\"), \"none\"),\n\t\t},\n\t\t\"streamSettings\": stream,\n\t}\n\treturn &ParseResult{Outbound: ob, Identity: identity}, nil\n}\n\nfunc parseTrojan(link string) (*ParseResult, error) {\n\tu, err := url.Parse(link)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif u.Scheme != \"trojan\" {\n\t\treturn nil, fmt.Errorf(\"not trojan\")\n\t}\n\tpw := u.User.Username()\n\thost := u.Hostname()\n\tport := defaultPort(u.Port(), 443)\n\tparams := u.Query()\n\tnetwork := params.Get(\"type\")\n\tif network == \"\" {\n\t\tnetwork = \"tcp\"\n\t}\n\tsecurity := params.Get(\"security\")\n\tif security == \"\" {\n\t\tsecurity = \"tls\"\n\t}\n\tstream := buildStream(network, security)\n\tapplyTransport(stream, params)\n\tapplySecurity(stream, params)\n\tapplyFinalMask(stream, params)\n","sourceCodeStart":282,"sourceCodeEnd":318,"githubUrl":"https://github.com/MHSanaei/3x-ui/blob/ad32144c42455696ea9f14e12168beac3e25f5d2/internal/util/link/outbound.go#L282-L318","documentation":"Returned by parseTrojan when the parsed URL's scheme is not exactly 'trojan'. Same shape as the vless guard: ParseOutbound only sends trojan:// links here, so this fires when parseTrojan is invoked directly or a custom routing table misroutes a trojan-variant scheme (trojan-go://) into it. Pure input validation.","triggerScenarios":"Direct calls to parseTrojan with arbitrary URLs; routing trojan-go:// links to parseTrojan; a dispatcher prefix table matching 'trojan' as a substring (e.g. matching 'trojan-go://' with HasPrefix(\"trojan\")).","commonSituations":"Forks adding trojan-go support; test fixtures calling the inner parser; prefix checks that use HasPrefix(link, \"trojan\") without the '://' delimiter.","solutions":["Dispatch on the exact prefix 'trojan://' and use ParseOutbound for public parsing.","Give trojan-go:// links a dedicated parser — its query params differ from plain trojan.","Fix substring-based prefix checks to include the '//' boundary."],"exampleFix":"// before\ncase strings.HasPrefix(link, \"trojan\"):\n    return parseTrojan(link)\n\n// after\ncase strings.HasPrefix(link, \"trojan://\"):\n    return parseTrojan(link)","handlingStrategy":"validation","validationCode":"func isTrojanLink(link string) bool {\n    u, err := url.Parse(strings.TrimSpace(link))\n    return err == nil && strings.EqualFold(u.Scheme, \"trojan\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use ParseOutbound rather than parseTrojan directly.","Match 'trojan://' exactly; route trojan-go:// to its own parser.","Include the '//' boundary in all prefix checks."],"tags":["parsing","proxy-links","trojan","invariant"],"backgroundTag":null,"analyzedSha":"ad32144c42455696ea9f14e12168beac3e25f5d2","analyzedAt":"2026-08-15T11:13:23.905Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}