{"id":"370116b464558d22","repo":"brianc/node-postgres","slug":"sasl-scram-server-first-message-server-nonce-is","errorCode":null,"errorMessage":"SASL: SCRAM-SERVER-FIRST-MESSAGE: server nonce is too short","messagePattern":"SASL: SCRAM-SERVER-FIRST-MESSAGE: server nonce is too short","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/pg/lib/crypto/sasl.js","lineNumber":81,"sourceCode":"  if (session.message !== 'SASLInitialResponse') {\n    throw new Error('SASL: Last message was not SASLInitialResponse')\n  }\n  if (typeof password !== 'string') {\n    throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string')\n  }\n  if (password === '') {\n    throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a non-empty string')\n  }\n  if (typeof serverData !== 'string') {\n    throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: serverData must be a string')\n  }\n\n  const sv = parseServerFirstMessage(serverData)\n\n  if (!sv.nonce.startsWith(session.clientNonce)) {\n    throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: server nonce does not start with client nonce')\n  } else if (sv.nonce.length === session.clientNonce.length) {\n    throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: server nonce is too short')\n  }\n\n  const scramMaxIterations =\n    typeof session.scramMaxIterations === 'number' ? session.scramMaxIterations : DEFAULT_MAX_SCRAM_ITERATIONS\n  // a value of 0 disables the iteration count check\n  if (scramMaxIterations !== 0 && sv.iteration > scramMaxIterations) {\n    throw new Error(\n      'SASL: SCRAM-SERVER-FIRST-MESSAGE: iteration count ' +\n        sv.iteration +\n        ' exceeds scramMaxIterations of ' +\n        scramMaxIterations\n    )\n  }\n\n  const clientFirstMessageBare = 'n=*,r=' + session.clientNonce\n  const serverFirstMessage = 'r=' + sv.nonce + ',s=' + sv.salt + ',i=' + sv.iteration\n\n  // without channel binding:","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/brianc/node-postgres/blob/c5e8c9a57bff6d9160ec5dbd5c4f4c1e4c460711/packages/pg/lib/crypto/sasl.js#L63-L99","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Verify you are connecting to a genuine PostgreSQL backend (not a non-compliant proxy/mock).","If behind PgBouncer or a connection pooler, ensure it passes SCRAM messages through unmodified.","Enable SSL to protect the authentication exchange from tampering."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await client.connect();\n} catch (err) {\n  if (/server nonce is too short/i.test(err.message)) {\n    console.error('Server did not append its own nonce — non-compliant server or proxy.');\n  }\n  throw err;\n}","preventionTips":["Connect to a genuine, compliant PostgreSQL backend.","Ensure PgBouncer or poolers pass SCRAM messages through unmodified.","Use SSL to prevent message tampering during auth."],"tags":["sasl","security","authentication","nonce"],"analyzedSha":"c5e8c9a57bff6d9160ec5dbd5c4f4c1e4c460711","analyzedAt":"2026-08-03T18:47:28.334Z","schemaVersion":2}