brianc/node-postgres · error · Error

SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a

Error message

SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a non-empty string

What it means

Thrown during SCRAM session continuation (sasl.js:69-71) when the password is an empty string (''). While SASLprep technically allows empty strings, node-postgres rejects them because an empty password for a SCRAM-authenticated role almost always indicates a configuration mistake (no password set) rather than an intentional empty-password role. The guard fires right after the type check and before PBKDF2 key derivation.

Source

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

  return {
    mechanism,
    clientNonce,
    response: gs2Header + ',,n=*,r=' + clientNonce,
    message: 'SASLInitialResponse',
    scramMaxIterations,
  }
}

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(

View on GitHub (pinned to c5e8c9a57b)

Solutions

  1. Set a non-empty password via PGPASSWORD, the connection string, or the config object.
  2. If using a password provider function, ensure it never returns '' for a SCRAM role.
  3. Check for accidental empty-string defaults in config spread/merge logic (e.g., { password: process.env.DB_PASSWORD || '' }).

Example fix

// before
const password = process.env.DB_PASSWORD || ''; // empty default
const client = new Client({ password });

// after
const password = process.env.DB_PASSWORD;
if (!password) throw new Error('DB_PASSWORD must be set');
const client = new Client({ password });
Defensive patterns

Strategy: validation

Validate before calling

function ensureNonEmptyPassword(config) {
  const pw = config.password ?? process.env.PGPASSWORD;
  if (pw === '') {
    throw new Error('Password is empty — set a non-empty password for SCRAM authentication');
  }
}

Type guard

function isNonEmptyPassword(pw) {
  return typeof pw === 'string' && pw.length > 0;
}

Try / catch

try {
  await client.connect();
} catch (err) {
  if (/password must be a non-empty string/i.test(err.message)) {
    console.error('Password resolved to empty string — check env/config for empty defaults.');
  }
  throw err;
}

Prevention

When it happens

Trigger: The password resolves to '' — e.g., PGPASSWORD='' or { password: '' } — for a role that requires SCRAM authentication. Also when a .pgpass file contains an empty entry or a password provider returns ''.

Common situations: An env var like DB_PASSWORD is set but empty in a CI/staging environment. A config merge overwrote the password with an empty default. The connection string has an empty password segment: postgres://user:@host/db.

Related errors


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