{"id":"b70ed92fdaa85586","repo":"jackc/pgx","slug":"invalid-scram-server-first-message-received-from-s","errorCode":null,"errorMessage":"invalid SCRAM server-first-message received from server: did not include r=","messagePattern":"invalid SCRAM server-first-message received from server: did not include r=","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pgconn/auth_scram.go","lineNumber":248,"sourceCode":"\tsc.clientFirstMessageBare = fmt.Appendf(nil, \"n=,r=%s\", sc.clientNonce)\n\n\tswitch {\n\tcase sc.authMechanism == scramSHA256PlusName:\n\t\tsc.clientGS2Header = []byte(\"p=tls-server-end-point,,\")\n\tcase sc.hasTLS:\n\t\tsc.clientGS2Header = []byte(\"y,,\")\n\tdefault:\n\t\tsc.clientGS2Header = []byte(\"n,,\")\n\t}\n\n\treturn append(sc.clientGS2Header, sc.clientFirstMessageBare...)\n}\n\nfunc (sc *scramClient) recvServerFirstMessage(serverFirstMessage []byte) error {\n\tsc.serverFirstMessage = serverFirstMessage\n\tbuf := serverFirstMessage\n\tif !bytes.HasPrefix(buf, []byte(\"r=\")) {\n\t\treturn errors.New(\"invalid SCRAM server-first-message received from server: did not include r=\")\n\t}\n\tbuf = buf[2:]\n\n\tidx := bytes.IndexByte(buf, ',')\n\tif idx == -1 {\n\t\treturn errors.New(\"invalid SCRAM server-first-message received from server: did not include s=\")\n\t}\n\tsc.clientAndServerNonce = buf[:idx]\n\tbuf = buf[idx+1:]\n\n\tif !bytes.HasPrefix(buf, []byte(\"s=\")) {\n\t\treturn errors.New(\"invalid SCRAM server-first-message received from server: did not include s=\")\n\t}\n\tbuf = buf[2:]\n\n\tidx = bytes.IndexByte(buf, ',')\n\tif idx == -1 {\n\t\treturn errors.New(\"invalid SCRAM server-first-message received from server: did not include i=\")","sourceCodeStart":230,"sourceCodeEnd":266,"githubUrl":"https://github.com/jackc/pgx/blob/ec1a0befd22592cffffdeeb0a50311b506372f4c/pgconn/auth_scram.go#L230-L266","documentation":"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.","triggerScenarios":"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.","commonSituations":"Custom PostgreSQL-compatible servers; debug proxies rewriting traffic; rare server bugs; network corruption. Almost never seen against vanilla PostgreSQL.","solutions":["Confirm you are connecting to a genuine PostgreSQL (or fully conformant fork) and not a corrupted/debug-proxied endpoint.","Remove any middleware that rewrites SASL payloads.","Capture the AuthenticationSASLContinue bytes to confirm the server is the source of the malformed message, then report upstream."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// SCRAM protocol errors are not retryable as-is; surface them clearly.\nif err := pgx.Connect(ctx, dsn); err != nil {\n    var pgErr *pgconn.PgError\n    if errors.As(err, &pgErr) {\n        // server-origin SQLSTATE error\n    } else if strings.Contains(err.Error(), \"invalid SCRAM server-first-message\") {\n        return fmt.Errorf(\"malformed SASL exchange (possible MITM/proxy): %w\", err)\n    }\n}","preventionTips":["Use sslmode=verify-full so SASL exchanges cannot be tampered with.","Avoid debug/proxy tools that rewrite SASL payloads on the wire.","Regression-test auth against your actual server build, not just vanilla PG."],"tags":["authentication","scram","protocol","security"],"analyzedSha":"ec1a0befd22592cffffdeeb0a50311b506372f4c","analyzedAt":"2026-08-04T22:52:11.263Z","schemaVersion":2}