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

  1. Inspect how many TCP passive :443 host candidates the answer carries; if legitimate, raise maxProxiedTCPCandidates (tcp_proxy.go, currently 16).
  2. 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.
  3. 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

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


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/a79b1bbef63dc836. Report an issue: GitHub.