{"record":{"id":"6a3a54945b786e66","repo":"golang/go","slug":"tls-handshake-buffer-not-empty-before-helloretryr","errorCode":null,"errorMessage":"tls: handshake buffer not empty before HelloRetryRequest","messagePattern":"tls: handshake buffer not empty before HelloRetryRequest","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/crypto/tls/handshake_server_tls13.go","lineNumber":533,"sourceCode":"\t\treturn nil\n\t}\n\tif hs.sentDummyCCS {\n\t\treturn nil\n\t}\n\ths.sentDummyCCS = true\n\n\treturn hs.c.writeChangeCipherRecord()\n}\n\nfunc (hs *serverHandshakeStateTLS13) doHelloRetryRequest(selectedGroup CurveID) (*keyShare, error) {\n\tc := hs.c\n\n\t// Make sure the client didn't send extra handshake messages alongside\n\t// their initial client_hello. If they sent two client_hello messages,\n\t// we will consume the second before they respond to the server_hello.\n\tif c.hand.Len() != 0 {\n\t\tc.sendAlert(alertUnexpectedMessage)\n\t\treturn nil, errors.New(\"tls: handshake buffer not empty before HelloRetryRequest\")\n\t}\n\n\t// The first ClientHello gets double-hashed into the transcript upon a\n\t// HelloRetryRequest. See RFC 8446, Section 4.4.1.\n\tif err := transcriptMsg(hs.clientHello, hs.transcript); err != nil {\n\t\treturn nil, err\n\t}\n\tchHash := hs.transcript.Sum(nil)\n\ths.transcript.Reset()\n\ths.transcript.Write([]byte{typeMessageHash, 0, 0, uint8(len(chHash))})\n\ths.transcript.Write(chHash)\n\n\thelloRetryRequest := &serverHelloMsg{\n\t\tvers:              hs.hello.vers,\n\t\trandom:            helloRetryRequestRandom,\n\t\tsessionId:         hs.hello.sessionId,\n\t\tcipherSuite:       hs.hello.cipherSuite,\n\t\tcompressionMethod: hs.hello.compressionMethod,","sourceCodeStart":515,"sourceCodeEnd":551,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/crypto/tls/handshake_server_tls13.go#L515-L551","documentation":"Before sending a HelloRetryRequest, the server checks that the client did not piggyback extra handshake messages after the first ClientHello (c.hand.Len() must be 0). If the buffer is non-empty, the client violated protocol ordering and the server sends unexpected_message. This guards against a smuggled second ClientHello.","triggerScenarios":"Client sends a ClientHello followed immediately by another handshake message before waiting for the ServerHello/HelloRetryRequest.","commonSituations":"Buggy or non-compliant TLS clients, fuzzers, attackers probing handshake-state handling.","solutions":["Client must send exactly one ClientHello and wait for ServerHello/HelloRetryRequest before sending further handshake messages","Use a compliant TLS client library that respects handshake ordering"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := tlsConn.Handshake(); err != nil {\n    if strings.Contains(err.Error(), \"handshake buffer not empty before HelloRetryRequest\") {\n        log.Printf(\"client sent extra messages before HRR from %v\", remote)\n    }\n    c.Close()\n    return\n}","preventionTips":["Use a compliant TLS client that sends one ClientHello and waits for ServerHello/HRR","Never pipeline handshake messages ahead of the server's response"],"tags":["tls","go","hello-retry-request","handshake-state","compliance","handshake"],"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-12T08:17:17.861Z"}