jackc/pgx · error

invalid SCRAM server-first-message received from server: did

Error message

invalid SCRAM server-first-message received from server: did not include r=

What it means

Returned by recvServerFirstMessage when the SCRAM server-first-message does not begin with 'r=' (the combined nonce attribute). Per RFC 5802 the server-first-message MUST start with r=. Any other byte indicates a malformed message from the server or an active tampering attempt.

Source

Thrown at pgconn/auth_scram.go:248

	sc.clientFirstMessageBare = fmt.Appendf(nil, "n=,r=%s", sc.clientNonce)

	switch {
	case sc.authMechanism == scramSHA256PlusName:
		sc.clientGS2Header = []byte("p=tls-server-end-point,,")
	case sc.hasTLS:
		sc.clientGS2Header = []byte("y,,")
	default:
		sc.clientGS2Header = []byte("n,,")
	}

	return append(sc.clientGS2Header, sc.clientFirstMessageBare...)
}

func (sc *scramClient) recvServerFirstMessage(serverFirstMessage []byte) error {
	sc.serverFirstMessage = serverFirstMessage
	buf := serverFirstMessage
	if !bytes.HasPrefix(buf, []byte("r=")) {
		return errors.New("invalid SCRAM server-first-message received from server: did not include r=")
	}
	buf = buf[2:]

	idx := bytes.IndexByte(buf, ',')
	if idx == -1 {
		return errors.New("invalid SCRAM server-first-message received from server: did not include s=")
	}
	sc.clientAndServerNonce = buf[:idx]
	buf = buf[idx+1:]

	if !bytes.HasPrefix(buf, []byte("s=")) {
		return errors.New("invalid SCRAM server-first-message received from server: did not include s=")
	}
	buf = buf[2:]

	idx = bytes.IndexByte(buf, ',')
	if idx == -1 {
		return errors.New("invalid SCRAM server-first-message received from server: did not include i=")

View on GitHub (pinned to ec1a0befd2)

Solutions

  1. Confirm you are connecting to a genuine PostgreSQL (or fully conformant fork) and not a corrupted/debug-proxied endpoint.
  2. Remove any middleware that rewrites SASL payloads.
  3. Capture the AuthenticationSASLContinue bytes to confirm the server is the source of the malformed message, then report upstream.
Defensive patterns

Strategy: try-catch

Try / catch

// SCRAM protocol errors are not retryable as-is; surface them clearly.
if err := pgx.Connect(ctx, dsn); err != nil {
    var pgErr *pgconn.PgError
    if errors.As(err, &pgErr) {
        // server-origin SQLSTATE error
    } else if strings.Contains(err.Error(), "invalid SCRAM server-first-message") {
        return fmt.Errorf("malformed SASL exchange (possible MITM/proxy): %w", err)
    }
}

Prevention

When it happens

Trigger: Server sends an AuthenticationSASLContinue whose payload does not start with 'r='. Caused by a buggy/non-conformant server, a man-in-the-middle corrupting the SASL exchange, or a protocol-level proxy mangling the message.

Common situations: Custom PostgreSQL-compatible servers; debug proxies rewriting traffic; rare server bugs; network corruption. Almost never seen against vanilla PostgreSQL.

Related errors


AI-assisted analysis of jackc/pgx@ec1a0befd2 (2026-08-04). Data as JSON: /data/errors/b70ed92fdaa85586.json. Report an issue: GitHub.