brianc/node-postgres · error · Error

SASL: SCRAM-SERVER-FIRST-MESSAGE: server nonce is too short

Error message

SASL: SCRAM-SERVER-FIRST-MESSAGE: server nonce is too short

What it means

Thrown during SCRAM session continuation (sasl.js:80-81) when the server's nonce has the exact same length as the client nonce — meaning the server appended nothing. Per RFC 5802, the server MUST append its own per-session nonce to the client's nonce to ensure freshness; a server that returns only the client nonce (no additional entropy) is non-compliant and could indicate a broken or malicious endpoint. The check is sv.nonce.length === session.clientNonce.length.

Source

Thrown at packages/pg/lib/crypto/sasl.js:81

  if (session.message !== 'SASLInitialResponse') {
    throw new Error('SASL: Last message was not SASLInitialResponse')
  }
  if (typeof password !== 'string') {
    throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string')
  }
  if (password === '') {
    throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a non-empty string')
  }
  if (typeof serverData !== 'string') {
    throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: serverData must be a string')
  }

  const sv = parseServerFirstMessage(serverData)

  if (!sv.nonce.startsWith(session.clientNonce)) {
    throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: server nonce does not start with client nonce')
  } else if (sv.nonce.length === session.clientNonce.length) {
    throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: server nonce is too short')
  }

  const scramMaxIterations =
    typeof session.scramMaxIterations === 'number' ? session.scramMaxIterations : DEFAULT_MAX_SCRAM_ITERATIONS
  // a value of 0 disables the iteration count check
  if (scramMaxIterations !== 0 && sv.iteration > scramMaxIterations) {
    throw new Error(
      'SASL: SCRAM-SERVER-FIRST-MESSAGE: iteration count ' +
        sv.iteration +
        ' exceeds scramMaxIterations of ' +
        scramMaxIterations
    )
  }

  const clientFirstMessageBare = 'n=*,r=' + session.clientNonce
  const serverFirstMessage = 'r=' + sv.nonce + ',s=' + sv.salt + ',i=' + sv.iteration

  // without channel binding:

View on GitHub (pinned to c5e8c9a57b)

Solutions

  1. Verify you are connecting to a genuine PostgreSQL backend (not a non-compliant proxy/mock).
  2. If behind PgBouncer or a connection pooler, ensure it passes SCRAM messages through unmodified.
  3. Enable SSL to protect the authentication exchange from tampering.
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await client.connect();
} catch (err) {
  if (/server nonce is too short/i.test(err.message)) {
    console.error('Server did not append its own nonce — non-compliant server or proxy.');
  }
  throw err;
}

Prevention

When it happens

Trigger: The server's r= value equals the clientNonce exactly (same length, starts with it per the previous check but adds no server-side entropy). This would mean the server sent back r=<clientNonce> with nothing appended.

Common situations: A poorly implemented PostgreSQL-compatible proxy or mock that does not generate a server nonce. A replay device that echoes the client's first message. Extremely rare with genuine PostgreSQL servers.

Related errors


AI-assisted analysis of brianc/node-postgres@c5e8c9a57b (2026-08-03). Data as JSON: /data/errors/370116b464558d22.json. Report an issue: GitHub.