jackc/pgx · error
invalid SCRAM nonce: did not include server nonce
Error message
invalid SCRAM nonce: did not include server nonce
What it means
Returned by recvServerFirstMessage when the combined nonce (r=) starts with the client nonce but is not longer than it, meaning the server appended no nonce of its own. RFC 5802 requires the server to contribute its own nonce; absence indicates a non-conformant or rogue server.
Source
Thrown at pgconn/auth_scram.go:300
sc.iterations, err = strconv.Atoi(string(iterationsStr))
if err != nil || sc.iterations <= 0 {
return fmt.Errorf("invalid SCRAM iteration count received from server: %w", err)
}
// Bound server-supplied iteration count to prevent a malicious server from forcing the client
// to spend unbounded CPU in PBKDF2. PostgreSQL's scram_iterations defaults to 4096; this ceiling
// is ~2500x that.
const maxScramIterations = 10_000_000
if sc.iterations > maxScramIterations {
return fmt.Errorf("SCRAM iteration count from server too high: %d (max %d)", sc.iterations, maxScramIterations)
}
if !bytes.HasPrefix(sc.clientAndServerNonce, sc.clientNonce) {
return errors.New("invalid SCRAM nonce: did not start with client nonce")
}
if len(sc.clientAndServerNonce) <= len(sc.clientNonce) {
return errors.New("invalid SCRAM nonce: did not include server nonce")
}
return nil
}
func (sc *scramClient) clientFinalMessage() string {
// The c= attribute carries the base64-encoded channel binding input.
//
// Without channel binding this is just the GS2 header alone ("biws" for
// "n,," or "eSws" for "y,,").
//
// With channel binding, this is the GS2 header with the channel binding data
// (certificate hash) appended.
channelBindInput := sc.clientGS2Header
if sc.authMechanism == scramSHA256PlusName {
channelBindInput = slices.Concat(sc.clientGS2Header, sc.channelBindingData)
}
channelBindingEncoded := base64.StdEncoding.EncodeToString(channelBindInput)View on GitHub (pinned to ec1a0befd2)
Solutions
- Verify the backend is a conformant PostgreSQL SCRAM implementation that generates a server nonce.
- Rule out MITM/proxy reorigination of the SASL message.
- Capture the handshake and report the non-conformance.
Defensive patterns
Strategy: try-catch
Try / catch
if err := connect(); err != nil && strings.Contains(err.Error(), "did not include server nonce") {
return fmt.Errorf("server failed to contribute a SCRAM nonce (non-conformant/MITM): %w", err)
} Prevention
- Ensure a conformant PostgreSQL backend that appends its own nonce.
- Use TLS certificate verification end-to-end.
- Capture the handshake to confirm the server is the source.
When it happens
Trigger: Server echoes exactly the client nonce (r=<clientNonce>) without appending a server portion. Detected by the length check len(clientAndServerNonce) <= len(clientNonce).
Common situations: Buggy server/proxy that parrots the client nonce; rogue endpoint; corrupted exchange.
Related errors
- invalid SCRAM server-first-message received from server: did
- invalid SCRAM server-first-message received from server: did
- invalid SCRAM server-first-message received from server: did
- invalid SCRAM nonce: did not start with client nonce
- invalid SCRAM server-final-message received from server
AI-assisted analysis of jackc/pgx@ec1a0befd2 (2026-08-04).
Data as JSON: /data/errors/023329622956843e.json.
Report an issue: GitHub.