{"record":{"id":"3e9fad2182a3e04b","repo":"BloopAI/vibe-kanban","slug":"server-proof-verification-failed","errorCode":null,"errorMessage":"Server proof verification failed.","messagePattern":"Server proof verification failed\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"packages/web-core/src/shared/dialogs/settings/settings/useRelayRemoteHostMutations.ts","lineNumber":96,"sourceCode":"      client_id: relayClientIdentity.clientId,\n      client_name: relayClientIdentity.clientName,\n      client_browser: relayClientIdentity.clientBrowser,\n      client_os: relayClientIdentity.clientOs,\n      client_device: relayClientIdentity.clientDevice,\n      public_key_b64: publicKeyB64,\n      client_proof_b64: clientProofB64,\n    }\n  );\n\n  const serverProofValid = await verifyServerProof(\n    sharedKey,\n    startData.enrollment_id,\n    publicKeyBytes,\n    finishData.server_public_key_b64,\n    finishData.server_proof_b64\n  );\n  if (!serverProofValid) {\n    throw new Error('Server proof verification failed.');\n  }\n\n  await savePairedRelayHost({\n    host_id: hostId,\n    host_name: hostName,\n    client_id: relayClientIdentity.clientId,\n    client_name: relayClientIdentity.clientName,\n    signing_session_id: finishData.signing_session_id,\n    public_key_b64: publicKeyB64,\n    private_key_jwk: privateKeyJwk,\n    server_public_key_b64: finishData.server_public_key_b64,\n    paired_at: new Date().toISOString(),\n  });\n}\n\nexport function useRelayRemoteHostsQuery() {\n  return {\n    queryKey: RELAY_REMOTE_HOSTS_QUERY_KEY,","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/packages/web-core/src/shared/dialogs/settings/settings/useRelayRemoteHostMutations.ts#L78-L114","documentation":"During PAKE-based relay host pairing, pairRelayHost runs a SPAKE2 enrollment with the relay backend; after the finish step it verifies the server's proof against the derived shared key. This Error is thrown when verifyServerProof returns false, meaning the server's server_proof_b64 does not match what the client expects — the server could not prove it derived the same shared secret (or the messages were tampered with / mismatched protocol versions).","triggerScenarios":"Calling usePairRemoteCloudHostMutation with a hostId/hostName/normalizedCode where finishRelaySpake2Enrollment returns a server proof that fails verification: wrong or mistyped enrollment code variant, mismatched relay backend/client SPAKE2 implementations or versions, corrupted/intercepted server_message data, or pairing against a host whose session state expired between start and finish.","commonSituations":"Typo in the pairing code (code normalized differently on the two sides); pairing an older host running an incompatible relay protocol version; a man-in-the-middle or misconfigured relay proxy altering payloads; race where the enrollment session on the relay expired before finish was called.","solutions":["Re-enter the pairing code carefully (or regenerate it on the host) and retry the pairing flow","Verify the relay backend and the client are on compatible versions — upgrade the older side","Check that no proxy/HTTPS interceptor is rewriting or caching the enrollment responses","Clear stored paired-host state and start a fresh pairing session"],"exampleFix":"// before\nconst serverProofValid = await verifyServerProof(\n  sharedKey, startData.enrollment_id, publicKeyBytes,\n  finishData.server_public_key_b64, finishData.server_proof_b64\n);\nif (!serverProofValid) {\n  throw new Error('Server proof verification failed.');\n}\n// after\nconst serverProofValid = await verifyServerProof(\n  sharedKey, startData.enrollment_id, publicKeyBytes,\n  finishData.server_public_key_b64, finishData.server_proof_b64\n);\nif (!serverProofValid) {\n  throw new Error(\n    `Server proof verification failed for host ${hostId} ` +\n    '(check that the pairing code matches and host/client relay versions are compatible).'\n  );\n}","handlingStrategy":"try-catch","validationCode":"// Before pairing: ensure code normalization matches and host exists\nconst normalized = normalizedCode.trim().replace(/[-\\s]/g, '').toUpperCase();\nif (normalized.length === 0) throw new Error('Pairing code is empty');\nconst hosts = await listRelayHosts();\nif (!hosts.some((h) => h.id === hostId)) throw new Error('Unknown host');","typeGuard":"function isPairingFailure(e: unknown): e is Error & { step: 'server-proof' } {\n  return e instanceof Error && e.message.includes('Server proof verification failed');\n}","tryCatchPattern":"try {\n  await pairRelayHost({ hostId, hostName, normalizedCode });\n} catch (e) {\n  if (isPairingFailure(e)) {\n    // Do NOT save the host; prompt user to re-enter the code and retry.\n    showPairingError('Pairing code mismatch or incompatible host version. Please retry.');\n  }\n}","preventionTips":["Always regenerate a fresh pairing code instead of reusing an old one after a failure","Keep relay backend and client versions in lockstep","Never bypass or log the server proof check — it is the server-authentication step of PAKE","Avoid proxies/interceptors that can alter enrollment payloads during pairing"],"tags":["pake","relay","pairing","security","cryptography"],"backgroundTag":"server-proof-verification-failed","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}