router-for-me/CLIProxyAPI · error

SDP contains inconsistent bundled ICE credentials

Error message

SDP contains inconsistent bundled ICE credentials

What it means

Thrown while extracting ICE credentials when two m= sections in the bundled upstream answer carry different ice-ufrag/ice-pwd pairs. The proxy rewrites candidates for the whole bundle using one credential set (remote ufrag ':' local ufrag), so inconsistent credentials across media sections cannot be handled and are rejected as a protocol violation (BUNDLE requires identical credentials).

Source

Thrown at internal/client/codex/live/tcp_proxy.go:267

		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
}

func closeCandidateTunnels(tunnels []*tcpCandidateTunnel) error {
	var closeErrors []error
	for _, tunnel := range tunnels {
		if errClose := tunnel.Close(); errClose != nil {
			closeErrors = append(closeErrors, errClose)
		}
	}

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Inspect the per-m-line a=ice-ufrag/a=ice-pwd values in the answer SDP and confirm they are identical
  2. Report the non-BUNDLE-compliant answer to the upstream producer
  3. Workaround: disable TCP proxying for the affected session
Defensive patterns

Strategy: validation

Validate before calling

// Verify identical credentials across bundled m-lines before proxying
seen := map[iceCredentials]bool{}
for _, m := range sdp.MediaDescriptions {
    u, _ := m.Attribute("ice-ufrag")
    p, _ := m.Attribute("ice-pwd")
    if u != "" || p != "" {
        seen[iceCredentials{u, p}] = true
    }
}
if len(seen) > 1 {
    return errors.New("non-BUNDLE-compliant answer; proxy will reject")
}

Type guard

func hasConsistentBundledCredentials(pairs []iceCredentials) bool {
    first := ""
    for _, c := range pairs {
        if c.ufrag == "" { continue }
        if first == "" { first = c.ufrag + ":" + c.password; continue }
        if c.ufrag+":"+c.password != first { return false }
    }
    return true
}

Try / catch

if _, _, err := live.PrepareProxiedUpstreamAnswer(answer, offer, dialer); err != nil {
    if strings.Contains(err.Error(), "inconsistent bundled ICE credentials") {
        return fmt.Errorf("upstream violated BUNDLE credential rules; report to SDP producer: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Upstream answer with multiple m= lines where at least one defines credentials differing from the others, while the TCP proxy dialer is enabled.

Common situations: Upstream server bug, non-BUNDLE-compliant SDP, or SDP mangling by an intermediary that rewrites one media section's credentials.

Related errors


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