{"id":"6858bd8e95450e54","repo":"brianc/node-postgres","slug":"sasl-scram-server-first-message-salt-must-be-bas","errorCode":null,"errorMessage":"SASL: SCRAM-SERVER-FIRST-MESSAGE: salt must be base64","messagePattern":"SASL: SCRAM-SERVER-FIRST-MESSAGE: salt must be base64","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"packages/pg/lib/crypto/sasl.js","lineNumber":205,"sourceCode":"      return [name, value]\n    })\n  )\n}\n\nfunction parseServerFirstMessage(data) {\n  const attrPairs = parseAttributePairs(data)\n\n  const nonce = attrPairs.get('r')\n  if (!nonce) {\n    throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: nonce missing')\n  } else if (!isPrintableChars(nonce)) {\n    throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: nonce must only contain printable characters')\n  }\n  const salt = attrPairs.get('s')\n  if (!salt) {\n    throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: salt missing')\n  } else if (!isBase64(salt)) {\n    throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: salt must be base64')\n  }\n  const iterationText = attrPairs.get('i')\n  if (!iterationText) {\n    throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: iteration missing')\n  } else if (!/^[1-9][0-9]*$/.test(iterationText)) {\n    throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: invalid iteration count')\n  }\n  const iteration = parseInt(iterationText, 10)\n\n  return {\n    nonce,\n    salt,\n    iteration,\n  }\n}\n\nfunction parseServerFinalMessage(serverData) {\n  const attrPairs = parseAttributePairs(serverData)","sourceCodeStart":187,"sourceCodeEnd":223,"githubUrl":"https://github.com/brianc/node-postgres/blob/c5e8c9a57bff6d9160ec5dbd5c4f4c1e4c460711/packages/pg/lib/crypto/sasl.js#L187-L223","documentation":"Thrown by parseServerFirstMessage during SCRAM-SHA-256 authentication when the salt attribute (`s=`) from the server's first SASL message fails the base64 format check at sasl.js:204 (regex isBase64 at line 172). A compliant PostgreSQL server always emits a valid base64 salt, so this almost always indicates a corrupted or non-conformant server response rather than a client misconfiguration.","triggerScenarios":"Reached when continueSession (sasl.js:62) is invoked during client.connect() and the parsed `s=` value contains characters outside [A-Za-z0-9+/] or has incorrect `=` padding. The salt is then unusable for the PBKDF2 key derivation at line 116.","commonSituations":"PgBouncer or another pooler/proxy in transaction-pooling mode that does not relay SCRAM frames correctly; a TLS-terminating reverse proxy that rewrites or truncates the auth exchange; connecting to a non-PostgreSQL server speaking a SASL/SCRAM variant with a different encoding.","solutions":["Connect directly to the PostgreSQL primary (bypass the pooler/proxy) to isolate the mangling source.","Upgrade PgBouncer to >=1.10 (proper SCRAM-SHA-256 support) and confirm auth_type is not a legacy method re-deriving verifiers.","Verify the host/port in the connection string actually point at a PostgreSQL server, not a proxy.","If TLS is terminated by a proxy, terminate TLS directly at Postgres so SASL frames pass through unmodified."],"exampleFix":"// before\nconst client = new Client({ host: 'pgbouncer.internal', port: 6432 })\n// after\nconst client = new Client({ host: 'postgres-primary.internal', port: 5432 })","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await client.connect()\n} catch (err) {\n  if (/salt must be base64/.test(err.message)) {\n    // server SASL frame was malformed -- suspect a proxy/pooler\n    logger.error('SCRAM frame corruption from intermediary', { err })\n    return connectBypassingPooler()\n  }\n  throw err\n}","preventionTips":["Bypass transaction-poolers when debugging SCRAM auth failures.","Pin pooler versions that support SCRAM-SHA-256 passthrough (PgBouncer >= 1.10).","Terminate TLS directly at Postgres rather than at a proxy that may rewrite SASL frames."],"tags":["sasl","scram","authentication","postgres","connection","base64"],"analyzedSha":"c5e8c9a57bff6d9160ec5dbd5c4f4c1e4c460711","analyzedAt":"2026-08-03T18:47:28.334Z","schemaVersion":2}