MHSanaei/3x-ui · warning
not trojan
Error message
not trojan
What it means
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.
Source
Thrown at internal/util/link/outbound.go:300
"settings": map[string]any{
"address": host,
"port": port,
"id": id,
"flow": params.Get("flow"),
"encryption": firstNonEmpty(params.Get("encryption"), "none"),
},
"streamSettings": stream,
}
return &ParseResult{Outbound: ob, Identity: identity}, nil
}
func parseTrojan(link string) (*ParseResult, error) {
u, err := url.Parse(link)
if err != nil {
return nil, err
}
if u.Scheme != "trojan" {
return nil, fmt.Errorf("not trojan")
}
pw := u.User.Username()
host := u.Hostname()
port := defaultPort(u.Port(), 443)
params := u.Query()
network := params.Get("type")
if network == "" {
network = "tcp"
}
security := params.Get("security")
if security == "" {
security = "tls"
}
stream := buildStream(network, security)
applyTransport(stream, params)
applySecurity(stream, params)
applyFinalMask(stream, params)
View on GitHub (pinned to ad32144c42)
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.
Example fix
// before
case strings.HasPrefix(link, "trojan"):
return parseTrojan(link)
// after
case strings.HasPrefix(link, "trojan://"):
return parseTrojan(link) Defensive patterns
Strategy: validation
Validate before calling
func isTrojanLink(link string) bool {
u, err := url.Parse(strings.TrimSpace(link))
return err == nil && strings.EqualFold(u.Scheme, "trojan")
} Prevention
- Use ParseOutbound rather than parseTrojan directly.
- Match 'trojan://' exactly; route trojan-go:// to its own parser.
- Include the '//' boundary in all prefix checks.
When it happens
Trigger: 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")).
Common situations: Forks adding trojan-go support; test fixtures calling the inner parser; prefix checks that use HasPrefix(link, "trojan") without the '://' delimiter.
Related errors
AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15).
Data as JSON: /api/errors/1c4a21686f6807ec.
Report an issue: GitHub.