{"record":{"id":"cfdbc949513d11f3","repo":"router-for-me/CLIProxyAPI","slug":"upstream-webrtc-tcp-proxy-candidate-address-must-b-cfdbc9","errorCode":null,"errorMessage":"upstream WebRTC TCP proxy candidate address must be globally routable","messagePattern":"upstream WebRTC TCP proxy candidate address must be globally routable","errorType":"http","errorClass":null,"httpStatus":502,"severity":"error","filePath":"internal/client/codex/live/tcp_proxy.go","lineNumber":209,"sourceCode":"\tif candidate.NetworkType() != ice.NetworkTypeTCP4 && candidate.NetworkType() != ice.NetworkTypeTCP6 {\n\t\treturn tcpCandidatePlan{}, false, nil\n\t}\n\tif candidate.TCPType() != ice.TCPTypePassive {\n\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) {","sourceCodeStart":191,"sourceCodeEnd":227,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/client/codex/live/tcp_proxy.go#L191-L227","documentation":"Thrown when a proxied TCP candidate parsed to a valid IP but isPublicProxyTarget() rejected it: the address is not globally routable — it is private (RFC1918), loopback, link-local, multicast, unspecified, or not global-unicast. The proxy deliberately refuses to forward to non-public addresses to avoid becoming an open relay into private networks.","triggerScenarios":"Upstream SDP advertises a host candidate with an internal address (10.x, 192.168.x, 127.0.0.1, 169.254.x, ::1, fc00::/7, etc.) on TCP passive port 443 while the TCP proxy dialer is enabled.","commonSituations":"The 'server' is actually behind NAT and leaks its LAN address into the SDP; a test rig advertises loopback candidates; the explicitly named disable-private-remote-ips / allow-private-remote-ips relay settings interact with deployments that genuinely need private targets.","solutions":["Make the upstream endpoint advertise its public IP in the SDP (configure its advertised/external address)","If you intentionally need private-address proxying in a controlled environment, review the codex.live-media-relay private-IP settings and network posture — note the proxy still rejects non-routable targets by design","Otherwise disable TCP proxying for this session and connect directly on the private network"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Verify the candidate target is globally routable before proxying\nfields := strings.Fields(candidateLine)\naddr, err := netip.ParseAddr(fields[4])\nif err != nil || !addr.Unmap().IsGlobalUnicast() || addr.IsPrivate() || addr.IsLoopback() || addr.IsLinkLocalUnicast() {\n    log.Warn().Str(\"candidate\", candidateLine).Msg(\"non-routable candidate; proxy will reject\")\n}","typeGuard":"func isProxyableCandidateAddr(s string) bool {\n    addr, err := netip.ParseAddr(s)\n    if err != nil {\n        return false\n    }\n    addr = addr.Unmap()\n    return addr.IsValid() && addr.IsGlobalUnicast() && !addr.IsUnspecified() &&\n        !addr.IsLoopback() && !addr.IsPrivate() && !addr.IsLinkLocalUnicast() &&\n        !addr.IsLinkLocalMulticast() && !addr.IsMulticast()\n}","tryCatchPattern":"if _, _, err := live.PrepareProxiedUpstreamAnswer(answer, offer, dialer); err != nil {\n    if strings.Contains(err.Error(), \"globally routable\") {\n        return fmt.Errorf(\"upstream advertises private address in SDP; configure its public IP: %w\", err)\n    }\n    return err\n}","preventionTips":["Configure upstream WebRTC endpoints to advertise their public/external IP","Never point the TCP proxy at NAT-internal addresses; it refuses by design as an anti-SSRF measure","In lab setups, run the media path on routable addresses or disable proxying"],"tags":["webrtc","security","ice-candidates","nat","tcp-proxy"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}