{"record":{"id":"39367565345242fc","repo":"router-for-me/CLIProxyAPI","slug":"upstream-webrtc-tcp-proxy-candidate-uses-disallowe","errorCode":null,"errorMessage":"upstream WebRTC TCP proxy candidate uses disallowed port %d","messagePattern":"upstream WebRTC TCP proxy candidate uses disallowed port (.+?)","errorType":"http","errorClass":null,"httpStatus":502,"severity":"error","filePath":"internal/client/codex/live/tcp_proxy.go","lineNumber":201,"sourceCode":"}\n\nfunc proxiedTCPCandidatePlan(rawCandidate string) (tcpCandidatePlan, bool, error) {\n\ttrimmed := strings.TrimSpace(rawCandidate)\n\tcandidate, errCandidate := ice.UnmarshalCandidate(trimmed)\n\tif errCandidate != nil {\n\t\treturn tcpCandidatePlan{}, false, fmt.Errorf(\"parse upstream WebRTC candidate: %w\", errCandidate)\n\t}\n\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}","sourceCodeStart":183,"sourceCodeEnd":219,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/client/codex/live/tcp_proxy.go#L183-L219","documentation":"Security policy of the TCP proxy: only TCP host candidates listening on port 443 are proxied (so tunneled traffic looks like ordinary HTTPS). Any TCP candidate advertising a different port is rejected outright with this error instead of being ignored.","triggerScenarios":"A candidate in the upstream answer is TCP4/TCP6, passive, component RTP, host type — but its port differs from 443 (e.g. 3478, 8443, or an ephemeral port).","commonSituations":"Upstream rotates its media ingress to a non-443 TCP port during an infrastructure change; a test fixture copied from a UDP/TURN setup uses 3478.","solutions":["Confirm the upstream's current TCP candidate port from its published docs/behavior; if it moved off 443, update the check (and the isPublicProxyTarget policy) deliberately in tcp_proxy.go.","If the candidate is not needed, make the filter skip non-443 TCP candidates (return keep=false) instead of erroring — but only if the upstream guarantees a 443 candidate exists, since the code later requires at least one.","For test fixtures, set the TCP candidate port to 443."],"exampleFix":"// before\nif candidate.Port() != 443 {\n\treturn tcpCandidatePlan{}, false, fmt.Errorf(\"upstream WebRTC TCP proxy candidate uses disallowed port %d\", candidate.Port())\n}\n\n// after: skip instead of fail (only if a 443 candidate is otherwise guaranteed)\nif candidate.Port() != 443 {\n\treturn tcpCandidatePlan{}, false, nil\n}","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"disallowed port\") {\n\tlog.Warn(\"upstream moved TCP candidates off port 443; TCP proxy unavailable this session\")\n\treturn err // or fall back to UDP/direct path if architecturally allowed\n}","preventionTips":["The 443-only rule is a deliberate egress-camouflage policy — change it only via an explicit security review.","Watch upstream release notes for media port changes."],"tags":["webrtc","tcp-proxy","security-policy","port-443","codex-live"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}