brianc/node-postgres · critical · Error
SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature is missin
Error message
SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature is missing
What it means
Thrown by parseServerFinalMessage when the final SASL message contains neither `e=` nor `v=` (sasl.js:231, serverSignature is falsy and error was absent). RFC 5802 requires at least one of them, so a frame with neither is non-conformant.
Source
Thrown at packages/pg/lib/crypto/sasl.js:232
return {
nonce,
salt,
iteration,
}
}
function parseServerFinalMessage(serverData) {
const attrPairs = parseAttributePairs(serverData)
const error = attrPairs.get('e')
const serverSignature = attrPairs.get('v')
if (error) {
throw new Error(`SASL: SCRAM-SERVER-FINAL-MESSAGE: server returned error: "${error}"`)
}
if (!serverSignature) {
throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature is missing')
} else if (!isBase64(serverSignature)) {
throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature must be base64')
}
return {
serverSignature,
}
}
function xorBuffers(a, b) {
if (!Buffer.isBuffer(a)) {
throw new TypeError('first argument must be a Buffer')
}
if (!Buffer.isBuffer(b)) {
throw new TypeError('second argument must be a Buffer')
}
if (a.length !== b.length) {
throw new Error('Buffer lengths must match')
}View on GitHub (pinned to c5e8c9a57b)
Solutions
- Connect directly to PostgreSQL bypassing any pooler/proxy.
- Confirm the pooler/proxy version supports SCRAM-SHA-256 passthrough end-to-end.
- Inspect wire frames to verify the final message is delivered intact.
Defensive patterns
Strategy: try-catch
Try / catch
try {
await client.connect()
} catch (err) {
if (/server signature is missing/.test(err.message)) {
logger.error('SCRAM final frame had neither v= nor e=', { err })
return connectBypassingPooler()
}
throw err
} Prevention
- Keep the auth path free of intermediaries that rewrite final SASL frames.
- Verify pooler SCRAM-SHA-256 end-to-end support.
- Capture the full final frame when this fires to confirm truncation.
When it happens
Trigger: finalizeSession parses a final server message whose only attribute pairs are unrelated to `v` or `e`; serverSignature = attrPairs.get('v') at line 225 returns undefined and error at line 224 is also undefined.
Common situations: Frame truncation by a pooler/proxy; non-PostgreSQL server; intermediary rewriting the final SASL message.
Related errors
- SASL: SCRAM-SERVER-FIRST-MESSAGE: salt must be base64
- SASL: SCRAM-SERVER-FIRST-MESSAGE: iteration missing
- SASL: SCRAM-SERVER-FIRST-MESSAGE: invalid iteration count
- SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature must be b
- SASL: Only mechanism(s) ${candidates.join(' and ')} are supp
AI-assisted analysis of brianc/node-postgres@c5e8c9a57b (2026-08-03).
Data as JSON: /data/errors/02487e9378f404a8.json.
Report an issue: GitHub.