{"record":{"id":"cf9021c37d309340","repo":"router-for-me/CLIProxyAPI","slug":"read-upstream-webrtc-answer-ice-credentials-w","errorCode":null,"errorMessage":"read upstream WebRTC answer ICE credentials: %w","messagePattern":"read upstream WebRTC answer ICE credentials: %w","errorType":"http","errorClass":null,"httpStatus":502,"severity":"error","filePath":"internal/client/codex/live/tcp_proxy.go","lineNumber":105,"sourceCode":"\tfields         []string\n\ttarget         netip.AddrPort\n}\n\nfunc prepareProxiedUpstreamAnswer(answer, localOffer string, dialer proxy.ContextDialer) (string, []*tcpCandidateTunnel, error) {\n\tif dialer == nil {\n\t\treturn \"\", nil, errors.New(\"Codex live TCP proxy dialer is unavailable\")\n\t}\n\tvar remoteDescription sdp.SessionDescription\n\tif errUnmarshal := remoteDescription.UnmarshalString(answer); errUnmarshal != nil {\n\t\treturn \"\", nil, fmt.Errorf(\"parse upstream WebRTC answer for TCP proxy: %w\", errUnmarshal)\n\t}\n\tvar localDescription sdp.SessionDescription\n\tif errUnmarshal := localDescription.UnmarshalString(localOffer); errUnmarshal != nil {\n\t\treturn \"\", nil, fmt.Errorf(\"parse upstream WebRTC offer for TCP proxy: %w\", errUnmarshal)\n\t}\n\tremoteCredentials, errCredentials := bundledICECredentials(&remoteDescription)\n\tif errCredentials != nil {\n\t\treturn \"\", nil, fmt.Errorf(\"read upstream WebRTC answer ICE credentials: %w\", errCredentials)\n\t}\n\tlocalCredentials, errCredentials := bundledICECredentials(&localDescription)\n\tif errCredentials != nil {\n\t\treturn \"\", nil, fmt.Errorf(\"read upstream WebRTC offer ICE credentials: %w\", errCredentials)\n\t}\n\n\tplans := make([]tcpCandidatePlan, 0, 4)\n\tcandidateCount := 0\n\tfor mediaIndex, media := range remoteDescription.MediaDescriptions {\n\t\tif media == nil {\n\t\t\tcontinue\n\t\t}\n\t\tfiltered := make([]sdp.Attribute, 0, len(media.Attributes))\n\t\tfor attributeIndex := range media.Attributes {\n\t\t\tattribute := media.Attributes[attributeIndex]\n\t\t\tif !attribute.IsICECandidate() {\n\t\t\t\tfiltered = append(filtered, attribute)\n\t\t\t\tcontinue","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/client/codex/live/tcp_proxy.go#L87-L123","documentation":"After parsing the upstream answer, the proxy extracts the ICE ufrag/password (bundledICECredentials) to validate incoming TCP tunnel connections. If the answer's media sections lack ice-ufrag/ice-pwd attributes (or they are malformed), credential extraction fails and the proxy aborts.","triggerScenarios":"The upstream answer SDP parses but contains no session-level or media-level a=ice-ufrag / a=ice-pwd attributes — e.g. a trickle-ICE answer that defers credentials, or an m-line with missing ICE attributes.","commonSituations":"Upstream changed to trickle ICE or a different SDP shape; attributes present only at session level while the parser expects them per media section (or vice versa); a stripped-down test SDP used in development.","solutions":["Dump the answer SDP and check each m= section for a=ice-ufrag and a=ice-pwd.","If upstream now uses trickle ICE or session-level-only credentials, extend bundledICECredentials to fall back to session-level attributes.","If the upstream regression is real, pin/roll back to the upstream version that emits full credentials."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Check the answer carries ICE credentials before proxying\nhasICE := false\nfor _, m := range parsed.MediaDescriptions {\n\tfor _, a := range m.Attributes {\n\t\tif a.Key == \"ice-ufrag\" || a.Key == \"ice-pwd\" {\n\t\t\thasICE = true\n\t\t}\n\t}\n}\nif !hasICE { /* skip TCP proxying, use default path */ }","typeGuard":null,"tryCatchPattern":"if _, _, err := prepareProxiedUpstreamAnswer(answer, offer, dialer); err != nil {\n\tif strings.Contains(err.Error(), \"ICE credentials\") {\n\t\tlog.Warn(\"answer lacks ICE credentials; falling back to direct connection\")\n\t\treturn useDirectPath(answer)\n\t}\n}","preventionTips":["Monitor upstream SDP shape changes (trickle ICE) in canary sessions.","Keep a fixture of a known-good answer in tests to detect regressions early."],"tags":["webrtc","ice","sdp","codex-live","tcp-proxy"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}