{"id":"837da76b8d8f91a5","repo":"brianc/node-postgres","slug":"sasl-scram-server-final-message-server-signature-837da7","errorCode":null,"errorMessage":"SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature must be base64","messagePattern":"SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature must be base64","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"packages/pg/lib/crypto/sasl.js","lineNumber":234,"sourceCode":"    nonce,\n    salt,\n    iteration,\n  }\n}\n\nfunction parseServerFinalMessage(serverData) {\n  const attrPairs = parseAttributePairs(serverData)\n  const error = attrPairs.get('e')\n  const serverSignature = attrPairs.get('v')\n\n  if (error) {\n    throw new Error(`SASL: SCRAM-SERVER-FINAL-MESSAGE: server returned error: \"${error}\"`)\n  }\n\n  if (!serverSignature) {\n    throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature is missing')\n  } else if (!isBase64(serverSignature)) {\n    throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature must be base64')\n  }\n  return {\n    serverSignature,\n  }\n}\n\nfunction xorBuffers(a, b) {\n  if (!Buffer.isBuffer(a)) {\n    throw new TypeError('first argument must be a Buffer')\n  }\n  if (!Buffer.isBuffer(b)) {\n    throw new TypeError('second argument must be a Buffer')\n  }\n  if (a.length !== b.length) {\n    throw new Error('Buffer lengths must match')\n  }\n  if (a.length === 0) {\n    throw new Error('Buffers cannot be empty')","sourceCodeStart":216,"sourceCodeEnd":252,"githubUrl":"https://github.com/brianc/node-postgres/blob/c5e8c9a57bff6d9160ec5dbd5c4f4c1e4c460711/packages/pg/lib/crypto/sasl.js#L216-L252","documentation":"Thrown when the `v=` (server signature) attribute is present in the final SASL message but fails the base64 format check at sasl.js:233 (isBase64 at line 172). Same root-cause family as the salt error (20) but on the final message.","triggerScenarios":"finalizeSession receives a final frame whose `v=` value contains characters outside the base64 alphabet or has malformed padding, so it cannot be compared against session.serverSignature at line 139.","commonSituations":"Proxy/pooler corruption of the final SASL frame; non-conformant server emitting an unencoded signature.","solutions":["Connect directly to PostgreSQL bypassing any pooler/proxy.","Confirm pooler/proxy SCRAM-SHA-256 passthrough support.","Verify TLS integrity end-to-end."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await client.connect()\n} catch (err) {\n  if (/server signature must be base64/.test(err.message)) {\n    logger.error('SCRAM final frame v= attribute was not base64 -- suspect intermediary', { err })\n    return connectBypassingPooler()\n  }\n  throw err\n}","preventionTips":["Terminate TLS directly at Postgres to avoid middlebox rewriting of SASL frames.","Pin pooler versions with correct SCRAM-SHA-256 passthrough.","Bypass the pooler when reproducing auth-layer errors."],"tags":["sasl","scram","authentication","connection","base64"],"analyzedSha":"c5e8c9a57bff6d9160ec5dbd5c4f4c1e4c460711","analyzedAt":"2026-08-03T18:47:28.334Z","schemaVersion":2}