{"record":{"id":"7f4f7b96ef970498","repo":"router-for-me/CLIProxyAPI","slug":"upstream-webrtc-tcp-proxy-candidate-is-malformed","errorCode":null,"errorMessage":"upstream WebRTC TCP proxy candidate is malformed","messagePattern":"upstream WebRTC TCP proxy candidate is malformed","errorType":"http","errorClass":null,"httpStatus":502,"severity":"error","filePath":"internal/client/codex/live/tcp_proxy.go","lineNumber":213,"sourceCode":"\t\treturn tcpCandidatePlan{}, false, nil\n\t}\n\tif candidate.Component() != uint16(ice.ComponentRTP) || candidate.Type() != ice.CandidateTypeHost {\n\t\treturn tcpCandidatePlan{}, false, nil\n\t}\n\tif candidate.Port() != 443 {\n\t\treturn tcpCandidatePlan{}, false, fmt.Errorf(\"upstream WebRTC TCP proxy candidate uses disallowed port %d\", candidate.Port())\n\t}\n\taddress, errAddress := netip.ParseAddr(candidate.Address())\n\tif errAddress != nil {\n\t\treturn tcpCandidatePlan{}, false, errors.New(\"upstream WebRTC TCP proxy candidate address must be an IP\")\n\t}\n\taddress = address.Unmap()\n\tif !isPublicProxyTarget(address) {\n\t\treturn tcpCandidatePlan{}, false, errors.New(\"upstream WebRTC TCP proxy candidate address must be globally routable\")\n\t}\n\tfields := strings.Fields(trimmed)\n\tif len(fields) < 8 {\n\t\treturn tcpCandidatePlan{}, false, errors.New(\"upstream WebRTC TCP proxy candidate is malformed\")\n\t}\n\treturn tcpCandidatePlan{\n\t\tfields: fields,\n\t\ttarget: netip.AddrPortFrom(address, uint16(candidate.Port())),\n\t}, true, nil\n}\n\nfunc isPublicProxyTarget(address netip.Addr) bool {\n\tif !address.IsValid() || !address.IsGlobalUnicast() || address.IsUnspecified() || address.IsLoopback() ||\n\t\taddress.IsPrivate() || address.IsLinkLocalUnicast() || address.IsLinkLocalMulticast() || address.IsMulticast() {\n\t\treturn false\n\t}\n\tfor _, prefix := range nonRoutableProxyTargetPrefixes {\n\t\tif prefix.Contains(address) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true","sourceCodeStart":195,"sourceCodeEnd":231,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/client/codex/live/tcp_proxy.go#L195-L231","documentation":"Thrown when a proxied TCP candidate passed all earlier checks but splitting the raw candidate line on whitespace produced fewer than 8 fields. A well-formed ICE candidate has at least 'candidate:<foundation> <component> <transport> <priority> <address> <port> typ <type>' = 8 fields; fewer means the line is truncated or non-standard even though the pion parser accepted it.","triggerScenarios":"An upstream SDP candidate line with missing components, e.g. missing 'typ host' tail or a malformed generation extension, while the TCP proxy dialer is enabled.","commonSituations":"Interoperability bugs in the upstream server's SDP generation, or middleboxes/proxies rewriting and truncating candidate lines.","solutions":["Log the raw candidate line at debug level and compare it against RFC 8839 candidate grammar","Report the malformed SDP to the upstream (Codex service) — the relay cannot safely rewrite a malformed line","As a workaround, disable TCP proxying for sessions from that upstream version"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Validate candidate line shape before the proxy path sees it\nfor _, line := range strings.Split(answerSDP, \"\\r\\n\") {\n    t := strings.TrimSpace(line)\n    if strings.HasPrefix(t, \"a=candidate:\") && len(strings.Fields(t)) < 8 {\n        return fmt.Errorf(\"malformed candidate line: %q\", t)\n    }\n}","typeGuard":"func isWellFormedCandidate(line string) bool {\n    t := strings.TrimSpace(line)\n    return strings.HasPrefix(t, \"a=candidate:\") && len(strings.Fields(t)) >= 8\n}","tryCatchPattern":"if _, _, err := live.PrepareProxiedUpstreamAnswer(answer, offer, dialer); err != nil {\n    if strings.Contains(err.Error(), \"malformed\") {\n        log.WithError(err).Warn(\"upstream sent malformed candidate; skipping TCP proxy\")\n        return applyDirect(answer)\n    }\n    return err\n}","preventionTips":["Validate SDP candidate grammar (RFC 8839) when logging upstream answers","Watch for middleboxes that truncate SDP lines; capture signaling untouched for comparison"],"tags":["webrtc","sdp","ice-candidates","parsing","tcp-proxy"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}