brianc/node-postgres · critical · Error
SASL: SCRAM-SERVER-FIRST-MESSAGE: invalid iteration count
Error message
SASL: SCRAM-SERVER-FIRST-MESSAGE: invalid iteration count
What it means
Thrown when the iteration count attribute is present but does not match the regex `^[1-9][0-9]*$` (sasl.js:210) — meaning it is zero, zero-prefixed, negative, or non-numeric. This is distinct from the separate upper-bound check at line 87 which is governed by scramMaxIterations.
Source
Thrown at packages/pg/lib/crypto/sasl.js:211
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) {
throw new Error(`SASL: SCRAM-SERVER-FINAL-MESSAGE: server returned error: "${error}"`)
}View on GitHub (pinned to c5e8c9a57b)
Solutions
- Connect directly to PostgreSQL bypassing any pooler/proxy.
- If the server is a real Postgres, inspect the wire frames (e.g. libpcap) to confirm the intermediary is not rewriting `i=`.
- Note: tuning scramMaxIterations will NOT fix this — that flag only affects the upper-bound check, not the syntax check.
Defensive patterns
Strategy: try-catch
Try / catch
try {
await client.connect()
} catch (err) {
if (/invalid iteration count/.test(err.message)) {
// note: scramMaxIterations does NOT affect this regex check
logger.error('Server sent a syntactically invalid i= attribute', { err })
return connectBypassingPooler()
}
throw err
} Prevention
- Do not conflate scramMaxIterations tuning with this error -- the flag only governs the upper-bound check at sasl.js:87.
- Capture wire frames when this fires to confirm intermediary rewriting.
- Keep the auth path free of middleboxes that rewrite SASL attributes.
When it happens
Trigger: Server sends `i=0`, `i=-1024`, `i=04096`, `i=abc`, or any value failing the decimal regex at line 210. The iteration count is then unusable for deriveKey at line 116.
Common situations: Protocol corruption by an intermediary; a non-PostgreSQL server. Note: legitimate-but-excessive iteration counts hit a *different* error (line 88) governed by scramMaxIterations; this regex failure is purely about malformed syntax.
Related errors
- SASL: SCRAM-SERVER-FIRST-MESSAGE: salt must be base64
- SASL: SCRAM-SERVER-FIRST-MESSAGE: iteration missing
- 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/d162a71cdbdca0a1.json.
Report an issue: GitHub.