router-for-me/CLIProxyAPI · error
SDP contains incomplete ICE credentials
Error message
SDP contains incomplete ICE credentials
What it means
Thrown while extracting ICE credentials from the upstream SDP for TCP proxying: a media section (or session + media combination) supplied only one of ice-ufrag / ice-pwd. Both are required to authenticate the rewritten candidates, so the proxy refuses to continue with half-present credentials.
Source
Thrown at internal/client/codex/live/tcp_proxy.go:259
for _, media := range description.MediaDescriptions {
if media == nil {
continue
}
ufrag := sessionUfrag
if mediaUfrag, ok := media.Attribute("ice-ufrag"); ok {
ufrag = mediaUfrag
}
password := sessionPassword
if mediaPassword, ok := media.Attribute("ice-pwd"); ok {
password = mediaPassword
}
ufrag = strings.TrimSpace(ufrag)
password = strings.TrimSpace(password)
if ufrag == "" && password == "" {
continue
}
if ufrag == "" || password == "" {
return iceCredentials{}, errors.New("SDP contains incomplete ICE credentials")
}
current := iceCredentials{ufrag: ufrag, password: password}
if selected.ufrag == "" {
selected = current
continue
}
if selected != current {
return iceCredentials{}, errors.New("SDP contains inconsistent bundled ICE credentials")
}
}
if selected.ufrag == "" {
selected = iceCredentials{ufrag: strings.TrimSpace(sessionUfrag), password: strings.TrimSpace(sessionPassword)}
}
if selected.ufrag == "" || selected.password == "" {
return iceCredentials{}, errors.New("SDP is missing ICE credentials")
}
return selected, nil
}View on GitHub (pinned to 78f0c4079e)
Solutions
- Dump the answer SDP and check every a=ice-ufrag has a matching a=ice-pwd (session-level and per-m-line)
- Fix the SDP producer if it is under your control
- Otherwise disable TCP proxying for that session and report the malformed answer upstream
Defensive patterns
Strategy: validation
Validate before calling
// Check ICE credential completeness before proxying
ufrag := sdp.Session.Attribute("ice-ufrag")
pwd := sdp.Session.Attribute("ice-pwd")
for _, m := range sdp.MediaDescriptions {
if v, ok := m.Attribute("ice-ufrag"); ok { ufrag = v }
if v, ok := m.Attribute("ice-pwd"); ok { pwd = v }
}
if (ufrag == "") != (pwd == "") {
return errors.New("answer SDP has half-present ICE credentials; proxy will reject")
} Type guard
func hasCompleteIceCredentials(ufrag, pwd string) bool {
return (strings.TrimSpace(ufrag) == "") == (strings.TrimSpace(pwd) == "")
} Try / catch
if _, _, err := live.PrepareProxiedUpstreamAnswer(answer, offer, dialer); err != nil {
if strings.Contains(err.Error(), "incomplete ICE credentials") {
return fmt.Errorf("upstream SDP bug (missing ice-ufrag or ice-pwd); disable proxying or fix producer: %w", err)
}
return err
} Prevention
- Pair every a=ice-ufrag with a=ice-pwd in generated SDP
- Reject half-credentialed SDP early in tests for your own signaling code
When it happens
Trigger: Upstream answer SDP where a m= section defines ice-ufrag without ice-pwd or vice versa (including the case where one comes from the session level and the other is absent everywhere).
Common situations: Buggy upstream SDP generation, or a signaling middlebox stripping one of the attributes; extremely rarely, hand-crafted SDP in tests.
Related errors
- SDP contains inconsistent bundled ICE credentials
- read upstream WebRTC answer ICE credentials: %w
- read upstream WebRTC offer ICE credentials: %w
- upstream WebRTC answer has no supported public TCP passive c
- upstream WebRTC TCP proxy candidate address must be an IP
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/0ed8dbf798569d4c.
Report an issue: GitHub.