{"record":{"id":"6fe1cdcba167e7f7","repo":"schollz/croc","slug":"sender-did-not-secure-the-channel","errorCode":null,"errorMessage":"Sender did not secure the channel","messagePattern":"Sender did not secure the channel","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/protocol/client.ts","lineNumber":728,"sourceCode":"    }\n    requirePakeVersion(confirmationB.v);\n    if (!(await wasm().confirmPeerKey(peerKeys.confirmationB, confirmationB.b))) {\n      throw new Error(\"Sender PAKE confirmation failed\");\n    }\n    key = peerKeys.key;\n    data = await openDataConnections(\n      settings,\n      room,\n      dataPorts(relay.banner),\n      signal,\n    );\n    await sendControl(control, {\n      t: \"externalip\",\n      m: relay.externalIP,\n      b: peerPake.b,\n    }, key);\n    const peerIP = await receiveControl(control, key);\n    if (peerIP.t !== \"externalip\") throw new Error(\"Sender did not secure the channel\");\n\n    const fileInfo = await receiveControl(control, key);\n    if (fileInfo.t === \"error\") throw new Error(fileInfo.m || \"Sender cancelled\");\n    if (fileInfo.t !== \"fileinfo\" || !fileInfo.b) {\n      throw new Error(\"Sender did not provide file metadata\");\n    }\n    const sender = JSON.parse(textDecoder.decode(fileInfo.b)) as SenderInfoWire;\n    const offer = validateSenderInfo(sender);\n    callbacks.onStatus?.(\"Review the incoming files\");\n    const destination = await callbacks.onOffer(offer);\n    if (!destination) {\n      await sendControl(control, { t: \"error\", m: \"refusing files\" }, key);\n      throw new Error(\"Transfer refused\");\n    }\n\n    for (const folder of offer.emptyFolders) {\n      await destination.createEmptyFolder(folder);\n    }","sourceCodeStart":710,"sourceCodeEnd":746,"githubUrl":"https://github.com/schollz/croc/blob/e25f1bdc04f07f094d50b0a1bf67e2563944b57a/web/src/protocol/client.ts#L710-L746","documentation":"Thrown by the recipient when the first message received after key establishment (sent encrypted with the derived key) is not 'externalip'. Both peers exchange external IP announcements as the first encrypted control messages to confirm the channel works end-to-end; receiving anything else — typically a decrypt failure artifact, an error frame, or a desynchronized peer — means the encrypted control channel is not functioning as expected.","triggerScenarios":"Peer sends an 'error' control message instead (it aborted during data-connection setup); decrypt fails or produces a message whose type decodes to something unexpected because keys differ (should have failed at pake-confirm, but partial divergence is possible); relay injects a banner or close frame; peer implementation skips the externalip exchange.","commonSituations":"Sender fails at openDataConnections (relay port list invalid, data ports unreachable) and reports an error; version-skewed peer with a different post-handshake choreography; the control socket receives a relay-level close frame mistaken for a peer message.","solutions":["Check the sender side — a data-channel setup failure is the usual cause and the peer error tells you which port/relay failed","Verify the relay banner port list is valid and those ports are reachable by both peers","Upgrade both peers so the post-handshake message order (externalip <-> externalip) matches","Retry with a different relay to rule out relay-level frame injection or connection resets"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Pre-flight: verify the relay's data ports are parseable and reachable before handshake\nconst ports = dataPorts(relay.banner);\nif (ports.length === 0) throw new Error(\"relay banner has no data ports\");","typeGuard":"function isExternalIP(m: { t: string }): boolean { return m.t === \"externalip\"; }","tryCatchPattern":"catch (e) {\n  if (e instanceof Error && e.message === \"Sender did not secure the channel\") {\n    // peer failed post-handshake (usually data-channel setup): check peer logs / relay ports, retry\n  }\n  throw e;\n}","preventionTips":["Confirm relay data ports are open in both peers' firewalls before starting","Coordinate cancel UX so one peer aborting mid-setup does not surprise the other","Retry the whole transfer on this error; partial resume is not supported"],"tags":["pake","protocol","network","handshake"],"backgroundTag":null,"analyzedSha":"e25f1bdc04f07f094d50b0a1bf67e2563944b57a","analyzedAt":"2026-08-15T12:53:39.096Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}