BloopAI/vibe-kanban · error
Server message has invalid side identifier.
Error message
Server message has invalid side identifier.
What it means
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.
Source
Thrown at packages/web-core/src/shared/lib/relayPake.ts:81
passwordBytes,
passwordScalar,
xScalar,
clientMessageBytes: clientPointBytes,
},
clientMessageB64: bytesToBase64(clientMessage),
};
}
export async function finishSpake2Enrollment(
state: Spake2EnrollmentClientState,
serverMessageB64: string
): Promise<Uint8Array> {
const serverMessage = base64ToBytes(serverMessageB64);
if (serverMessage.length !== 33) {
throw new Error('Server message has invalid length.');
}
if (serverMessage[0] !== 0x42) {
throw new Error('Server message has invalid side identifier.');
}
const serverPointBytes = serverMessage.slice(1);
const serverPoint = ed25519.ExtendedPoint.fromHex(serverPointBytes);
const negativePasswordScalar =
(CURVE_ORDER - state.passwordScalar) % CURVE_ORDER;
const keyPoint = serverPoint
.add(SPAKE2_N.multiply(negativePasswordScalar))
.multiply(state.xScalar);
const keyPointBytes = keyPoint.toRawBytes();
return hashAb(
state.passwordBytes,
SPAKE2_CLIENT_ID,
SPAKE2_SERVER_ID,
state.clientMessageBytes,
serverPointBytes,View on GitHub (pinned to 4deb7eca8f)
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.
Example fix
// before const key = await finishSpake2Enrollment(state, echoBackMessage); // after const resp = await startRelaySpake2Enrollment(hostId, sessionId, req); // server message comes from THIS response const key = await finishSpake2Enrollment(state, resp.serverMessageB64);
Defensive patterns
Strategy: validation
Validate before calling
function hasServerSideTag(b64: string): boolean {
try {
const raw = atob(b64);
return raw.length === 33 && raw.charCodeAt(0) === 0x42;
} catch { return false; }
} Type guard
function isServerSideMessage(msg: Uint8Array): boolean {
return msg.length === 33 && msg[0] === 0x42; // 0x42 = 'B' (server side)
} Try / catch
try {
const key = await finishSpake2Enrollment(state, serverMessageB64);
} catch (e) {
if (e instanceof Error && e.message.includes('side identifier')) {
console.error('Got a non-server (0x41 client?) message; check message routing.');
restartPairing();
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Server message has invalid length.
- unsupported relay WS envelope version
- Enrollment code must be 6 characters.
- request_id called for unsupported request variant
- invalid close payload
AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29).
Data as JSON: /api/errors/93c6db047cdcf2c2.
Report an issue: GitHub.