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
- Update node-postgres (pg) to the latest version — this guard has existed since SCRAM support was added and internal fixes have shipped.
- Remove or audit any custom Client subclass or monkeypatch that overrides the SASL handshake (startSession/continueSession/finalizeSession calls).
- Eliminate proxies or SSL terminators between the client and PostgreSQL that might alter or truncate the SASL message stream.
- 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
- Connect directly to PostgreSQL without intermediary proxies that might corrupt the SASL stream.
- Keep node-postgres updated to the latest version.
- Avoid custom Client subclasses that override the authentication flow.
- Ensure pg-native bindings (if used) match your pg version.
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
- SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature does not
- SASL: text must be a string
- SASL: attribute pairs text must be a string
- SASL: Invalid attribute pair entry
- SASL: SCRAM-SERVER-FIRST-MESSAGE: nonce missing
AI-assisted analysis of brianc/node-postgres@ff9d775abd (2026-08-11).
Data as JSON: /api/errors/da2df546d5c89936.
Report an issue: GitHub.