{"record":{"id":"a79b1bbef63dc836","repo":"router-for-me/CLIProxyAPI","slug":"upstream-webrtc-answer-exceeds-the-d-tcp-candidat","errorCode":null,"errorMessage":"upstream WebRTC answer exceeds the %d TCP candidate proxy limit","messagePattern":"upstream WebRTC answer exceeds the (.+?) TCP candidate proxy limit","errorType":"http","errorClass":null,"httpStatus":502,"severity":"error","filePath":"internal/client/codex/live/tcp_proxy.go","lineNumber":137,"sourceCode":"\t\tfor attributeIndex := range media.Attributes {\n\t\t\tattribute := media.Attributes[attributeIndex]\n\t\t\tif !attribute.IsICECandidate() {\n\t\t\t\tfiltered = append(filtered, attribute)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcandidateCount++\n\t\t\tif candidateCount > maxUpstreamICECandidates {\n\t\t\t\treturn \"\", nil, fmt.Errorf(\"upstream WebRTC answer exceeds the %d candidate limit\", maxUpstreamICECandidates)\n\t\t\t}\n\t\t\tplan, keep, errCandidate := proxiedTCPCandidatePlan(attribute.Value)\n\t\t\tif errCandidate != nil {\n\t\t\t\treturn \"\", nil, errCandidate\n\t\t\t}\n\t\t\tif !keep {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif len(plans) >= maxProxiedTCPCandidates {\n\t\t\t\treturn \"\", nil, fmt.Errorf(\"upstream WebRTC answer exceeds the %d TCP candidate proxy limit\", maxProxiedTCPCandidates)\n\t\t\t}\n\t\t\tplan.mediaIndex = mediaIndex\n\t\t\tplan.attributeIndex = len(filtered)\n\t\t\tfiltered = append(filtered, attribute)\n\t\t\tplans = append(plans, plan)\n\t\t}\n\t\tmedia.Attributes = filtered\n\t}\n\tif len(plans) == 0 {\n\t\treturn \"\", nil, errors.New(\"upstream WebRTC answer has no supported public TCP passive candidate on port 443\")\n\t}\n\n\texpectedUser := remoteCredentials.ufrag + \":\" + localCredentials.ufrag\n\ttunnels := make([]*tcpCandidateTunnel, 0, len(plans))\n\tcloseTunnels := func() {\n\t\tfor _, tunnel := range tunnels {\n\t\t\tif errClose := tunnel.Close(); errClose != nil {\n\t\t\t\tlog.WithError(errClose).Debug(\"codex live TCP proxy: close candidate tunnel after setup error\")","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/client/codex/live/tcp_proxy.go#L119-L155","documentation":"Second DoS cap: only TCP passive host candidates on port 443 are proxied, and at most maxProxiedTCPCandidates (16) tunnels will be created. Exceeding that count aborts the whole rewrite with this error.","triggerScenarios":"More than 16 candidates in the answer match the proxy filter (TCP4/TCP6, passive, component RTP, host type, port 443) so len(plans) would exceed 16.","commonSituations":"Upstream deployed a large anycast/pool of TCP host candidates; misconfigured test SDP repeating the same candidate many times.","solutions":["Inspect how many TCP passive :443 host candidates the answer carries; if legitimate, raise maxProxiedTCPCandidates (tcp_proxy.go, currently 16).","Alternatively keep the cap and take only the first N plans (change the hard error into truncation) if proxying all candidates is not required — but that changes security posture, so prefer raising the cap.","Report candidate flooding to the upstream if the volume looks anomalous."],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"TCP candidate proxy limit\") {\n\t// decide policy: raise maxProxiedTCPCandidates or reject session\n\treturn err\n}","preventionTips":["Track how many TCP passive :443 host candidates your upstream typically sends; size the cap with headroom.","Re-test with tcp_proxy_test.go fixtures after changing either candidate constant."],"tags":["webrtc","tcp-proxy","dos-protection","codex-live","limits"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}