router-for-me/CLIProxyAPI · error
upstream WebRTC answer exceeds the %d TCP candidate proxy li
Error message
upstream WebRTC answer exceeds the %d TCP candidate proxy limit
What it means
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.
Source
Thrown at internal/client/codex/live/tcp_proxy.go:137
for attributeIndex := range media.Attributes {
attribute := media.Attributes[attributeIndex]
if !attribute.IsICECandidate() {
filtered = append(filtered, attribute)
continue
}
candidateCount++
if candidateCount > maxUpstreamICECandidates {
return "", nil, fmt.Errorf("upstream WebRTC answer exceeds the %d candidate limit", maxUpstreamICECandidates)
}
plan, keep, errCandidate := proxiedTCPCandidatePlan(attribute.Value)
if errCandidate != nil {
return "", nil, errCandidate
}
if !keep {
continue
}
if len(plans) >= maxProxiedTCPCandidates {
return "", nil, fmt.Errorf("upstream WebRTC answer exceeds the %d TCP candidate proxy limit", maxProxiedTCPCandidates)
}
plan.mediaIndex = mediaIndex
plan.attributeIndex = len(filtered)
filtered = append(filtered, attribute)
plans = append(plans, plan)
}
media.Attributes = filtered
}
if len(plans) == 0 {
return "", nil, errors.New("upstream WebRTC answer has no supported public TCP passive candidate on port 443")
}
expectedUser := remoteCredentials.ufrag + ":" + localCredentials.ufrag
tunnels := make([]*tcpCandidateTunnel, 0, len(plans))
closeTunnels := func() {
for _, tunnel := range tunnels {
if errClose := tunnel.Close(); errClose != nil {
log.WithError(errClose).Debug("codex live TCP proxy: close candidate tunnel after setup error")View on GitHub (pinned to 78f0c4079e)
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.
Defensive patterns
Strategy: validation
Try / catch
if err != nil && strings.Contains(err.Error(), "TCP candidate proxy limit") {
// decide policy: raise maxProxiedTCPCandidates or reject session
return err
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: Upstream deployed a large anycast/pool of TCP host candidates; misconfigured test SDP repeating the same candidate many times.
Related errors
- upstream WebRTC answer exceeds the %d candidate limit
- parse upstream WebRTC answer for TCP proxy: %w
- parse upstream WebRTC offer for TCP proxy: %w
- read upstream WebRTC answer ICE credentials: %w
- read upstream WebRTC offer ICE credentials: %w
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/a79b1bbef63dc836.
Report an issue: GitHub.