brianc/node-postgres · critical · Error

SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature does not

Error message

SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature does not match

What it means

Thrown during SCRAM session finalization (sasl.js:139-141) when the server's signature (v= attribute in the SCRAM-SERVER-FINAL-MESSAGE) does not match the signature the client computed locally. The server signature is an HMAC-SHA-256 over the auth message using the Server Key derived from the password; a mismatch means the password is wrong, or the server is not the legitimate holder of the stored verifier. This is the primary 'authentication failed' signal in SCRAM-SHA-256 and indicates incorrect credentials with high confidence.

Source

Thrown at packages/pg/lib/crypto/sasl.js:140

  const serverSignatureBytes = await crypto.hmacSha256(serverKey, authMessage)

  session.message = 'SASLResponse'
  session.serverSignature = Buffer.from(serverSignatureBytes).toString('base64')
  session.response = clientFinalMessageWithoutProof + ',p=' + clientProof
}

function finalizeSession(session, serverData) {
  if (session.message !== 'SASLResponse') {
    throw new Error('SASL: Last message was not SASLResponse')
  }
  if (typeof serverData !== 'string') {
    throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: serverData must be a string')
  }

  const { serverSignature } = parseServerFinalMessage(serverData)

  if (serverSignature !== session.serverSignature) {
    throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature does not match')
  }
}

/**
 * printable       = %x21-2B / %x2D-7E
 *                   ;; Printable ASCII except ",".
 *                   ;; Note that any "printable" is also
 *                   ;; a valid "value".
 */
function isPrintableChars(text) {
  if (typeof text !== 'string') {
    throw new TypeError('SASL: text must be a string')
  }
  return text
    .split('')
    .map((_, i) => text.charCodeAt(i))
    .every((c) => (c >= 0x21 && c <= 0x2b) || (c >= 0x2d && c <= 0x7e))
}

View on GitHub (pinned to c5e8c9a57b)

Solutions

  1. Verify the password is correct: test with psql using the same credentials.
  2. Check for trailing whitespace/newlines in the password from env vars or config files (trim() it).
  3. Reset the role password on the server and update the client configuration to match.

Example fix

// before
const password = fs.readFileSync('.pgpass', 'utf8'); // may have trailing \n

// after
const password = fs.readFileSync('.pgpass', 'utf8').trim();
// or reset on server:
// ALTER ROLE myuser WITH PASSWORD 'correctpass';
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await client.connect();
} catch (err) {
  if (/server signature does not match/i.test(err.message)) {
    console.error('Authentication failed: wrong password or corrupted credentials.');
    // prompt for correct credentials or rotate
  }
  throw err;
}

Prevention

When it happens

Trigger: The password provided by the client does not match the PostgreSQL role's stored SCRAM verifier. The client computed session.serverSignature during continueSession and compares it against the v= value from the server's final message in finalizeSession.

Common situations: Wrong password (typo, stale credential, rotated password not updated in config). The role's password was changed on the server but the client config/env var still has the old one. Copy-paste introduced a trailing newline or space in the password. Connecting to the wrong database/role.

Related errors


AI-assisted analysis of brianc/node-postgres@c5e8c9a57b (2026-08-03). Data as JSON: /data/errors/322d45a9882deeb2.json. Report an issue: GitHub.