{"record":{"id":"322d45a9882deeb2","repo":"brianc/node-postgres","slug":"sasl-scram-server-final-message-server-signature","errorCode":null,"errorMessage":"SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature does not match","messagePattern":"SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature does not match","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"packages/pg/lib/crypto/sasl.js","lineNumber":140,"sourceCode":"  const serverSignatureBytes = await crypto.hmacSha256(serverKey, authMessage)\n\n  session.message = 'SASLResponse'\n  session.serverSignature = Buffer.from(serverSignatureBytes).toString('base64')\n  session.response = clientFinalMessageWithoutProof + ',p=' + clientProof\n}\n\nfunction finalizeSession(session, serverData) {\n  if (session.message !== 'SASLResponse') {\n    throw new Error('SASL: Last message was not SASLResponse')\n  }\n  if (typeof serverData !== 'string') {\n    throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: serverData must be a string')\n  }\n\n  const { serverSignature } = parseServerFinalMessage(serverData)\n\n  if (serverSignature !== session.serverSignature) {\n    throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature does not match')\n  }\n}\n\n/**\n * printable       = %x21-2B / %x2D-7E\n *                   ;; Printable ASCII except \",\".\n *                   ;; Note that any \"printable\" is also\n *                   ;; a valid \"value\".\n */\nfunction isPrintableChars(text) {\n  if (typeof text !== 'string') {\n    throw new TypeError('SASL: text must be a string')\n  }\n  return text\n    .split('')\n    .map((_, i) => text.charCodeAt(i))\n    .every((c) => (c >= 0x21 && c <= 0x2b) || (c >= 0x2d && c <= 0x7e))\n}","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/brianc/node-postgres/blob/ff9d775abd12f29dd6df03945253b54eabbb29f2/packages/pg/lib/crypto/sasl.js#L122-L158","documentation":"Thrown in finalizeSession() when the server's signature verifier (v= attribute) does not match the signature the client computed locally. During SCRAM-SHA-256, the client derives a server key from the password and computes an expected HMAC-SHA256 over the full auth message. If the server's signature differs, the server failed to prove it knows the password — meaning either the client sent the wrong password or the exchange was tampered with (MITM).","triggerScenarios":"session.serverSignature (computed at sasl.js:125 as HMAC-SHA256 of the server key over the auth message, base64-encoded) does not equal the serverSignature parsed from the server's final message at line 137. The comparison at line 139 fails.","commonSituations":"Wrong password supplied in the connection string or config (most common cause); password was changed on the server between connection pool creation and use; a connection pooler like PgBouncer in transaction mode interfering with SCRAM state; a TLS man-in-the-middle altering the auth exchange when channel binding is not in use.","solutions":["Verify the password is correct — connect with the same credentials via psql to confirm.","If using a connection pooler (PgBouncer, etc.), ensure it is configured for SCRAM auth passthrough or use session mode.","Check whether the password was recently rotated on the server and update your connection config.","Enable SSL/TLS to prevent MITM tampering; if channel binding is available, ensure the server certificate is valid so SCRAM-SHA-256-PLUS can be negotiated."],"exampleFix":"// before — wrong or stale password\nconst client = new Client({ host: 'db', user: 'app', password: process.env.DB_PASSWORD })\n// after — verify password works via psql first, then use the confirmed value\nconst client = new Client({ host: 'db', user: 'app', password: 'confirmed-correct-password' })","handlingStrategy":"try-catch","validationCode":"// Verify the password works before using it in a connection pool:\nconst { execSync } = require('child_process')\ntry {\n  execSync(`PGPASSWORD=${password} psql -h ${host} -U ${user} -d ${database} -c 'SELECT 1'`, { stdio: 'pipe' })\n} catch {\n  console.error('Password verification failed — credentials are incorrect')\n}","typeGuard":null,"tryCatchPattern":"try {\n  await client.connect()\n} catch (err) {\n  if (err.message.includes('server signature does not match')) {\n    // Most likely: wrong password or MITM tampering\n    throw new Error('Authentication failed: verify password or check for MITM')\n  }\n  throw err\n}","preventionTips":["Verify the password with psql before deploying.","Use a secrets manager to avoid stale or manually-entered passwords.","When rotating passwords, update all clients simultaneously.","Enable SSL/TLS to prevent man-in-the-middle attacks on the auth exchange.","If using PgBouncer, use session mode or verify SCRAM passthrough is configured."],"tags":["authentication","sasl","scram","password","security","connection"],"backgroundTag":null,"analyzedSha":"ff9d775abd12f29dd6df03945253b54eabbb29f2","analyzedAt":"2026-08-11T15:33:59.644Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}