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
- Verify the password is correct: test with psql using the same credentials.
- Check for trailing whitespace/newlines in the password from env vars or config files (trim() it).
- 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
- Verify credentials with psql using the same connection string before debugging code.
- Trim whitespace/newlines from passwords read from files or env vars.
- Use a secrets manager to avoid copy-paste errors and stale credentials.
- When rotating passwords, update all client configs atomically.
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
- SASL: SCRAM-SERVER-FINAL-MESSAGE: server returned error: "${
- SASL: Only mechanism(s) ${candidates.join(' and ')} are supp
- SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a
- SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a
- SASL: SCRAM-SERVER-FIRST-MESSAGE: server nonce does not star
AI-assisted analysis of brianc/node-postgres@c5e8c9a57b (2026-08-03).
Data as JSON: /data/errors/322d45a9882deeb2.json.
Report an issue: GitHub.