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

  1. Retry with matching croc versions on both ends
  2. Start a completely fresh transfer (new code, new room) rather than resuming half-open state
  3. 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

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


AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15). Data as JSON: /api/errors/6ef7c933b789a0bf. Report an issue: GitHub.