brianc/node-postgres · critical · Error
SASL: SCRAM-SERVER-FIRST-MESSAGE: iteration missing
Error message
SASL: SCRAM-SERVER-FIRST-MESSAGE: iteration missing
What it means
Thrown by parseServerFirstMessage when the server's first SCRAM message lacks the required `i=` (iteration count) attribute entirely (sasl.js:208, iterationText is falsy). RFC 5802 mandates this attribute, so its absence indicates a non-conformant or truncated server response.
Source
Thrown at packages/pg/lib/crypto/sasl.js:209
function parseServerFirstMessage(data) {
const attrPairs = parseAttributePairs(data)
const nonce = attrPairs.get('r')
if (!nonce) {
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: nonce missing')
} else if (!isPrintableChars(nonce)) {
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: nonce must only contain printable characters')
}
const salt = attrPairs.get('s')
if (!salt) {
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: salt missing')
} else if (!isBase64(salt)) {
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: salt must be base64')
}
const iterationText = attrPairs.get('i')
if (!iterationText) {
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: iteration missing')
} else if (!/^[1-9][0-9]*$/.test(iterationText)) {
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: invalid iteration count')
}
const iteration = parseInt(iterationText, 10)
return {
nonce,
salt,
iteration,
}
}
function parseServerFinalMessage(serverData) {
const attrPairs = parseAttributePairs(serverData)
const error = attrPairs.get('e')
const serverSignature = attrPairs.get('v')
if (error) {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.
- Verify TLS integrity end-to-end (no frame-rewriting middlebox).
Defensive patterns
Strategy: try-catch
Try / catch
try {
await client.connect()
} catch (err) {
if (/iteration missing/.test(err.message)) {
logger.error('SCRAM final frame missing i= attribute -- likely intermediary truncation', { err })
return connectDirectlyToPrimary()
}
throw err
} Prevention
- Avoid SCRAM-incompatible poolers in the auth path.
- Use direct TLS to Postgres to keep SASL frames intact.
- Smoke-test connect() against the primary in CI to surface intermediary issues early.
When it happens
Trigger: continueSession receives serverData with no `i=...` pair in the comma-separated attribute list produced by parseAttributePairs (sasl.js:175). attrPairs.get('i') returns undefined.
Common situations: Same proxy/pooler frame-mangling family as the salt error; a NAT/MITM device truncating the auth exchange; a server implementing an outdated or non-standard SASL profile.
Related errors
- SASL: SCRAM-SERVER-FIRST-MESSAGE: salt must be base64
- SASL: SCRAM-SERVER-FIRST-MESSAGE: invalid iteration count
- SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature is missin
- 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/90484998f8ecca96.json.
Report an issue: GitHub.