router-for-me/CLIProxyAPI · error
upstream WebRTC answer exceeds the %d candidate limit
Error message
upstream WebRTC answer exceeds the %d candidate limit
What it means
A defensive DoS cap: the proxy counts every a=candidate attribute in the upstream answer and hard-fails past maxUpstreamICECandidates (64). An answer with more than 64 candidates is treated as hostile or broken rather than being processed.
Source
Thrown at internal/client/codex/live/tcp_proxy.go:127
return "", nil, fmt.Errorf("read upstream WebRTC offer ICE credentials: %w", errCredentials)
}
plans := make([]tcpCandidatePlan, 0, 4)
candidateCount := 0
for mediaIndex, media := range remoteDescription.MediaDescriptions {
if media == nil {
continue
}
filtered := make([]sdp.Attribute, 0, len(media.Attributes))
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
}View on GitHub (pinned to 78f0c4079e)
Solutions
- Log the candidate count and inspect the answer; if legitimate, raise maxUpstreamICECandidates in tcp_proxy.go (it is a local const, currently 64).
- If unexpected, report the oversized SDP to the upstream service — normal Codex answers are far below 64 candidates.
- Rate-limit or reject the session at a higher layer if a peer repeatedly sends oversized answers.
Example fix
// before maxUpstreamICECandidates = 64 // after (only if upstream legitimately sends more) maxUpstreamICECandidates = 128
Defensive patterns
Strategy: validation
Validate before calling
// Cheap pre-count of candidates in the raw SDP
func candidateCount(sdp string) int { return strings.Count(sdp, "a=candidate:") + strings.Count(sdp, "a=candidate ") }
if candidateCount(answer) > maxUpstreamICECandidates { /* reject early */ } Try / catch
if err != nil && strings.Contains(err.Error(), "candidate limit") {
log.Warn("possible SDP flooding from upstream; dropping session")
return errSessionDropped
} Prevention
- Treat the 64-candidate cap as a security invariant — do not remove it to make an anomaly pass.
- Alert on repeated limit hits from the same upstream; it signals an upstream defect or attack.
When it happens
Trigger: The upstream answer contains more than 64 ICE candidate attributes across all media sections (count includes UDP and non-TCP candidates).
Common situations: Upstream infrastructure change flooding SDP with candidates; a malicious/buggy peer; a fuzzing or load test feeding oversized SDP.
Related errors
- upstream WebRTC answer exceeds the %d TCP candidate proxy li
- 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/b92bd99c36fac8c6.
Report an issue: GitHub.