schollz/croc · error · Error
Recipient did not secure the channel
Error message
Recipient did not secure the channel
What it means
After the data connections are opened, the first key-encrypted control message from the recipient must be of type 'externalip' (the peer IP exchange step). Receiving anything else means the channel encryption or message sequencing is off: the peer is not at the expected point in the protocol. Because decryption with the derived key apparently produced a framed message, this usually indicates version/order divergence rather than a bad key.
Source
Thrown at web/src/protocol/client.ts:462
throw new Error("Recipient PAKE confirmation failed");
}
await sendControl(control, {
t: "pake-confirm",
v: PAKE_PROTOCOL_VERSION,
b: peerKeys.confirmationB,
});
key = peerKeys.key;
callbacks.onStatus?.("Opening encrypted data channels…");
data = await openDataConnections(
settings,
room,
dataPorts(relay.banner),
signal,
);
const peerIP = await receiveControl(control, key);
if (peerIP.t !== "externalip") throw new Error("Recipient did not secure the channel");
await sendControl(control, { t: "externalip", m: relay.externalIP }, key);
await sendControl(control, {
t: "fileinfo",
b: textEncoder.encode(JSON.stringify(senderInfo(files))),
}, key);
let totalTransferred = 0;
for (;;) {
checkAbort(signal);
const message = await receiveControl(control, key);
if (message.t === "error") throw new Error(message.m || "Recipient refused transfer");
if (message.t === "finished") {
await sendControl(control, { t: "finished" }, key);
callbacks.onStatus?.("Transfer complete");
return;
}
if (message.t !== "recipientready" || !message.b) {
throw new Error(`Unexpected peer message: ${message.t}`);View on GitHub (pinned to e25f1bdc04)
Solutions
- Retry with matching croc versions on both ends
- Start a completely fresh transfer (new code, new room) rather than resuming half-open state
- Log message.t to see which frame arrived instead; an 'error' frame points at the recipient's failure
Defensive patterns
Strategy: try-catch
Type guard
function isExternalIP(msg) {
return !!msg && msg.t === "externalip";
} Try / catch
try {
await sendFiles(opts);
} catch (e) {
if (/did not secure the channel/.test(e.message)) {
showBanner("Secure channel could not be established. Restart the transfer.");
return;
}
throw e;
} Prevention
- Restart transfers end-to-end (new code, new sockets) after any mid-handshake failure
- Keep client versions aligned so post-handshake message order matches
- Log the unexpected message type to detect divergent peers early
When it happens
Trigger: A recipient that sends a different message first after data-channel setup (older/newer protocol); the control socket delivering an 'error' frame from the recipient; reconnect scenarios where one side restarts the handshake while the other continues.
Common situations: Mid-transfer reconnects; clients at different versions disagreeing on post-handshake ordering; recipient hitting an error and emitting an error frame that is read here.
Related errors
- Recipient did not start a croc PAKE handshake
- Peer sent an unexpected control handshake
- Recipient did not confirm the croc PAKE handshake
- Unexpected peer message: ${message.t}
- Sender did not complete the croc PAKE handshake
AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15).
Data as JSON: /api/errors/6ef7c933b789a0bf.
Report an issue: GitHub.