{"record":{"id":"a4cda4be80e66c15","repo":"BloopAI/vibe-kanban","slug":"server-message-has-invalid-length","errorCode":null,"errorMessage":"Server message has invalid length.","messagePattern":"Server message has invalid length\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/web-core/src/shared/lib/relayPake.ts","lineNumber":78,"sourceCode":"\n  return {\n    state: {\n      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,","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/packages/web-core/src/shared/lib/relayPake.ts#L60-L96","documentation":"finishSpake2Enrollment decodes the server's PAKE2 message from base64 and requires exactly 33 bytes (1 side byte + 32-byte Ed25519 point); any other length throws. It guards against corrupt or protocol-mismatched server responses before point arithmetic.","triggerScenarios":"serverMessageB64 returned by the relay server's /spake2/start response decodes to a length other than 33 — empty string (0 bytes), truncated/corrupted base64, a JSON error body mistakenly passed as the message, or a server speaking a different protocol version.","commonSituations":"Server returned an error payload instead of the PAKE message and the caller passed the wrong field; base64 with padding/encoding issues; version skew between client (0x42 + 32-byte point) and server message format; man-in-the-middle or proxy mangling the payload.","solutions":["Verify you pass the correct response field (the base64 server PAKE message), not the whole response body.","Check client/server protocol versions match (33-byte message = 1 tag byte + 32-byte point).","Log base64ToBytes(serverMessageB64).length on failure to diagnose whether it's truncated or a wrong payload.","Restart the enrollment: if the server errored, its session state may be invalid and startSpake2Enrollment must run again."],"exampleFix":"// before\nconst sharedKey = await finishSpake2Enrollment(state, serverPayload.data);\n// after\nif (!serverPayload?.serverMessageB64) {\n  throw new Error('No server PAKE message in response');\n}\nconst sharedKey = await finishSpake2Enrollment(state, serverPayload.serverMessageB64);","handlingStrategy":"validation","validationCode":"function looksLikeServerPakeMessage(b64: string): boolean {\n  try { return atob(b64).length === 33; } catch { return false; }\n}\nif (!looksLikeServerPakeMessage(serverMessageB64)) {\n  throw new Error('Malformed server PAKE message');\n}","typeGuard":"function isServerPakeMessage(msg: Uint8Array): boolean {\n  return msg.length === 33 && msg[0] === 0x42;\n}","tryCatchPattern":"try {\n  const key = await finishSpake2Enrollment(state, serverMessageB64);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('invalid length')) {\n    console.error('Server PAKE message malformed; protocol mismatch or error body passed.');\n    restartPairing();\n  }\n}","preventionTips":["Pass only the designated server message field from the response, never the whole body","Check protocol versions on client and server before pairing","Validate base64 decodes to 33 bytes before invoking","Restart the full enrollment flow after any PAKE failure — state is single-use"],"tags":["crypto","spake2","protocol","validation"],"backgroundTag":"protocol-payload-mismatch","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}