{"id":"d6fc97cccf9e7b89","repo":"brianc/node-postgres","slug":"sasl-scram-server-first-message-client-password","errorCode":null,"errorMessage":"SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string","messagePattern":"SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/pg/lib/crypto/sasl.js","lineNumber":67,"sourceCode":"\n  const clientNonce = crypto.randomBytes(18).toString('base64')\n  const gs2Header = mechanism === 'SCRAM-SHA-256-PLUS' ? 'p=tls-server-end-point' : stream ? 'y' : 'n'\n\n  return {\n    mechanism,\n    clientNonce,\n    response: gs2Header + ',,n=*,r=' + clientNonce,\n    message: 'SASLInitialResponse',\n    scramMaxIterations,\n  }\n}\n\nasync function continueSession(session, password, serverData, stream) {\n  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","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/brianc/node-postgres/blob/c5e8c9a57bff6d9160ec5dbd5c4f4c1e4c460711/packages/pg/lib/crypto/sasl.js#L49-L85","documentation":"Thrown during SCRAM session continuation (sasl.js:66-68) when the password used for authentication is not a string. The SCRAM-SHA-256 protocol requires the plaintext password to derive a salted key via PBKDF2, so a non-string (number, object, null, undefined) cannot be processed. This is a client-side guard fired before any crypto operation, ensuring the type contract is met.","triggerScenarios":"The client's password property is undefined (e.g., PGPASSWORD not set and no password in config), or it was set to a non-string value like a number or an object. This surfaces inside continueSession when typeof password !== 'string'.","commonSituations":"PGPASSWORD env var is unset and no password is in the connection string/config for a role requiring SCRAM auth. A dynamic password provider function was used incorrectly and returned a non-string. The password was accidentally set to null in a config merge.","solutions":["Ensure the password is a string: set PGPASSWORD, add it to the connection string, or pass { password: '...' } to the Client/Pool config.","If using a dynamic password provider (password as a function), make sure it resolves to a string.","Verify the .pgpass file is readable if relying on it, though note pgpass support is deprecated."],"exampleFix":"// before\nconst client = new Client({ user: 'me', host: 'localhost' }); // no password\n\n// after\nconst client = new Client({\n  user: 'me',\n  host: 'localhost',\n  password: process.env.PGPASSWORD,\n});","handlingStrategy":"type-guard","validationCode":"function ensureStringPassword(config) {\n  const pw = config.password ?? process.env.PGPASSWORD;\n  if (typeof pw !== 'string') {\n    throw new TypeError('Password must be a string for SCRAM authentication');\n  }\n  config.password = pw;\n}","typeGuard":"function isStringPassword(password) {\n  return typeof password === 'string';\n}","tryCatchPattern":"try {\n  await client.connect();\n} catch (err) {\n  if (/password must be a string/i.test(err.message)) {\n    console.error('Password is not set or not a string — check PGPASSWORD / config.');\n  }\n  throw err;\n}","preventionTips":["Always provide a string password via config, connection string, or PGPASSWORD.","If using a dynamic password function, ensure it resolves to a string.","Add a startup assertion that the password env var exists and is a string."],"tags":["sasl","authentication","password","validation"],"analyzedSha":"c5e8c9a57bff6d9160ec5dbd5c4f4c1e4c460711","analyzedAt":"2026-08-03T18:47:28.334Z","schemaVersion":2}