{"record":{"id":"93c6db047cdcf2c2","repo":"BloopAI/vibe-kanban","slug":"server-message-has-invalid-side-identifier","errorCode":null,"errorMessage":"Server message has invalid side identifier.","messagePattern":"Server message has invalid side identifier\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/web-core/src/shared/lib/relayPake.ts","lineNumber":81,"sourceCode":"      passwordBytes,\n      passwordScalar,\n      xScalar,\n      clientMessageBytes: clientPointBytes,\n    },\n    clientMessageB64: bytesToBase64(clientMessage),\n  };\n}\n\nexport async function finishSpake2Enrollment(\n  state: Spake2EnrollmentClientState,\n  serverMessageB64: string\n): Promise<Uint8Array> {\n  const serverMessage = base64ToBytes(serverMessageB64);\n  if (serverMessage.length !== 33) {\n    throw new Error('Server message has invalid length.');\n  }\n  if (serverMessage[0] !== 0x42) {\n    throw new Error('Server message has invalid side identifier.');\n  }\n\n  const serverPointBytes = serverMessage.slice(1);\n  const serverPoint = ed25519.ExtendedPoint.fromHex(serverPointBytes);\n  const negativePasswordScalar =\n    (CURVE_ORDER - state.passwordScalar) % CURVE_ORDER;\n\n  const keyPoint = serverPoint\n    .add(SPAKE2_N.multiply(negativePasswordScalar))\n    .multiply(state.xScalar);\n  const keyPointBytes = keyPoint.toRawBytes();\n\n  return hashAb(\n    state.passwordBytes,\n    SPAKE2_CLIENT_ID,\n    SPAKE2_SERVER_ID,\n    state.clientMessageBytes,\n    serverPointBytes,","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/packages/web-core/src/shared/lib/relayPake.ts#L63-L99","documentation":"finishSpake2Enrollment requires the first byte of the 33-byte server message to be 0x42 ('B', the server side identifier); any other tag byte throws. This detects messages from the wrong PAKE side or a foreign protocol.","triggerScenarios":"The decoded server message is 33 bytes but its first byte is not 0x42 — e.g. a client message (0x41) echoed back, a different protocol framing byte from a mismatched server version, or corrupted data that coincidentally has 33 bytes.","commonSituations":"Point-to-point plumbing bugs where the client's own message is fed back as the server response; relay forwarding messages out of order or between two pairing sessions; server upgraded to a new message tag while the browser bundle is stale/cached.","solutions":["Confirm the value passed is the SERVER's response message, not the client's clientMessageB64.","Hard-refresh the web app to clear a stale cached bundle that disagrees with the server's protocol version.","Check the relay server's SPAKE2 implementation writes 0x42 as the side byte.","Log serverMessage[0] and compare against expected 0x42 to identify what the server actually sent."],"exampleFix":"// before\nconst key = await finishSpake2Enrollment(state, echoBackMessage);\n// after\nconst resp = await startRelaySpake2Enrollment(hostId, sessionId, req); // server message comes from THIS response\nconst key = await finishSpake2Enrollment(state, resp.serverMessageB64);","handlingStrategy":"validation","validationCode":"function hasServerSideTag(b64: string): boolean {\n  try {\n    const raw = atob(b64);\n    return raw.length === 33 && raw.charCodeAt(0) === 0x42;\n  } catch { return false; }\n}","typeGuard":"function isServerSideMessage(msg: Uint8Array): boolean {\n  return msg.length === 33 && msg[0] === 0x42; // 0x42 = 'B' (server side)\n}","tryCatchPattern":"try {\n  const key = await finishSpake2Enrollment(state, serverMessageB64);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('side identifier')) {\n    console.error('Got a non-server (0x41 client?) message; check message routing.');\n    restartPairing();\n  }\n}","preventionTips":["Never pass the client's own clientMessageB64 back as the server message","Ensure the relay forwards each message to the correct pairing side","Refresh the app bundle when the server protocol version changes","Log msg[0] on failure to identify what was actually received"],"tags":["crypto","spake2","protocol"],"backgroundTag":"protocol-payload-mismatch","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}