{"record":{"id":"0fa29772733a7515","repo":"router-for-me/CLIProxyAPI","slug":"read-upstream-webrtc-offer-ice-credentials-w","errorCode":null,"errorMessage":"read upstream WebRTC offer ICE credentials: %w","messagePattern":"read upstream WebRTC offer ICE credentials: %w","errorType":"http","errorClass":null,"httpStatus":502,"severity":"error","filePath":"internal/client/codex/live/tcp_proxy.go","lineNumber":109,"sourceCode":"func 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\n\t\t\t}\n\t\t\tcandidateCount++\n\t\t\tif candidateCount > maxUpstreamICECandidates {\n\t\t\t\treturn \"\", nil, fmt.Errorf(\"upstream WebRTC answer exceeds the %d candidate limit\", maxUpstreamICECandidates)","sourceCodeStart":91,"sourceCodeEnd":127,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/client/codex/live/tcp_proxy.go#L91-L127","documentation":"Identical credential extraction, but for the local offer SDP. The proxy needs the local ICE password to validate that connections arriving on the loopback tunnel listener genuinely come from the local ICE agent. If the local offer lacks ICE credentials, the proxy cannot secure the tunnels and refuses to continue.","triggerScenarios":"localOffer parses as SDP but has no readable ice-ufrag/ice-pwd in its media descriptions — empty/placeholder offer, offer generated by a stack that omits ICE attributes, or the wrong string (e.g. a candidate list) was stored as the offer.","commonSituations":"Local offer built manually for testing without ICE attributes; session state populated from a different field; refactor changed which string is kept as the offer.","solutions":["Verify the stored localOffer is the localDescription.SDP of the real RTCPeerConnection.","Inspect it for a=ice-ufrag/a=ice-pwd lines; if absent, fix the offer generation path.","Add a unit test asserting bundledICECredentials succeeds on the offer you actually store."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"if !strings.Contains(s.localOffer, \"a=ice-pwd:\") {\n\treturn errors.New(\"local offer has no ICE password; regenerate offer\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always source the offer from a real RTCPeerConnection local description.","Never handcraft or round-trip the offer through lossy serialization."],"tags":["webrtc","ice","sdp","codex-live","tcp-proxy","session-state"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}