brianc/node-postgres · error · Error

SASL: SCRAM-SERVER-FIRST-MESSAGE: server nonce does not star

Error message

SASL: SCRAM-SERVER-FIRST-MESSAGE: server nonce does not start with client nonce

What it means

Thrown during SCRAM session continuation (sasl.js:78-79) when the server's combined nonce (r=...) does not begin with the client's nonce. Per RFC 5802, the server must return the client nonce unchanged with its own nonce appended; a server nonce that does not start with the client nonce indicates either a protocol violation or a potential man-in-the-middle/replay attack where the server response was substituted. The client sent r=<clientNonce> in the first message and verifies the server echoed it back as a prefix.

Source

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

async function continueSession(session, password, serverData, stream) {
  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

View on GitHub (pinned to c5e8c9a57b)

Solutions

  1. Verify there is no proxy or MITM between the client and PostgreSQL (check PgBouncer, load balancers, SSL termination).
  2. If using SSL, ensure it is properly established so the SCRAM exchange is integrity-protected.
  3. If connecting to a custom PostgreSQL-compatible server, verify its SCRAM implementation correctly echoes the client nonce.
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await client.connect();
} catch (err) {
  if (/server nonce does not start with client nonce/i.test(err.message)) {
    console.error('Possible MITM or non-compliant server in SCRAM exchange. Verify SSL and network path.');
  }
  throw err;
}

Prevention

When it happens

Trigger: The server's first SCRAM message contains an r= value whose first bytes do not match the clientNonce generated at sasl.js:50. This is checked via sv.nonce.startsWith(session.clientNonce).

Common situations: A buggy or non-compliant PostgreSQL server or proxy. An active MITM attempting to relay a different SCRAM session. Rare: data corruption on the wire truncating the nonce. This is extremely uncommon with real PostgreSQL servers.

Related errors


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