brianc/node-postgres · error · Error

SASL: SCRAM-SERVER-FINAL-MESSAGE: serverData must be a strin

Error message

SASL: SCRAM-SERVER-FINAL-MESSAGE: serverData must be a string

What it means

Thrown during the final step of SCRAM-SHA-256 authentication inside finalizeSession(). The function expects the server's final SASL message as a string argument, but received something else (null, undefined, Buffer, number). This is an internal invariant guard — node-postgres's own connection handler calls finalizeSession with the payload from the AuthenticationSASLFinal message, so a normal user almost never triggers it directly.

Source

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

  const saltedPassword = await crypto.deriveKey(saslprep(password), saltBytes, sv.iteration)
  const clientKey = await crypto.hmacSha256(saltedPassword, 'Client Key')
  const storedKey = await crypto.sha256(clientKey)
  const clientSignature = await crypto.hmacSha256(storedKey, authMessage)
  const clientProof = xorBuffers(Buffer.from(clientKey), Buffer.from(clientSignature)).toString('base64')
  const serverKey = await crypto.hmacSha256(saltedPassword, 'Server Key')
  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')

View on GitHub (pinned to ff9d775abd)

Solutions

  1. Update node-postgres (pg) to the latest version — this guard has existed since SCRAM support was added and internal fixes have shipped.
  2. Remove or audit any custom Client subclass or monkeypatch that overrides the SASL handshake (startSession/continueSession/finalizeSession calls).
  3. Eliminate proxies or SSL terminators between the client and PostgreSQL that might alter or truncate the SASL message stream.
  4. If using pg-native, verify the native binding version is compatible with your pg version.
Defensive patterns

Strategy: try-catch

Validate before calling

// This is an internal SASL error — pre-validation of server data is not possible.
// Instead, wrap the connection in a try-catch and verify server reachability:
const testConn = new Client(connStr)
try {
  await testConn.connect()
  await testConn.end()
} catch (e) {
  console.error('Connection failed:', e.message)
}

Try / catch

try {
  await client.connect()
} catch (err) {
  if (err.message.includes('serverData must be a string')) {
    // Internal SASL protocol error — likely a corrupting proxy or incompatible server
    console.error('SASL finalization failed — check for proxies or non-standard servers')
  }
  throw err
}

Prevention

When it happens

Trigger: The connection handler passes msg.data from the server's final SASL frame to finalizeSession(session, serverData). If msg.data is not a string — due to a protocol parser delivering a Buffer or null, a custom Client subclass overriding the auth flow, or a corrupted/partial auth exchange where the final message payload is empty.

Common situations: Using a forked or heavily patched pg version with a broken SASL handler; an SSL terminator or proxy (e.g., a custom TLS proxy) that strips or corrupts the final SASL frame; connecting to a server that abruptly closes the connection mid-auth, leaving the final payload null. Extremely rare with stock pg against real PostgreSQL.

Related errors


AI-assisted analysis of brianc/node-postgres@ff9d775abd (2026-08-11). Data as JSON: /api/errors/da2df546d5c89936. Report an issue: GitHub.