golang/go · error

tls: handshake buffer not empty before HelloRetryRequest

Error message

tls: handshake buffer not empty before HelloRetryRequest

What it means

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.

Source

Thrown at src/crypto/tls/handshake_server_tls13.go:533

		return nil
	}
	if hs.sentDummyCCS {
		return nil
	}
	hs.sentDummyCCS = true

	return hs.c.writeChangeCipherRecord()
}

func (hs *serverHandshakeStateTLS13) doHelloRetryRequest(selectedGroup CurveID) (*keyShare, error) {
	c := hs.c

	// Make sure the client didn't send extra handshake messages alongside
	// their initial client_hello. If they sent two client_hello messages,
	// we will consume the second before they respond to the server_hello.
	if c.hand.Len() != 0 {
		c.sendAlert(alertUnexpectedMessage)
		return nil, errors.New("tls: handshake buffer not empty before HelloRetryRequest")
	}

	// The first ClientHello gets double-hashed into the transcript upon a
	// HelloRetryRequest. See RFC 8446, Section 4.4.1.
	if err := transcriptMsg(hs.clientHello, hs.transcript); err != nil {
		return nil, err
	}
	chHash := hs.transcript.Sum(nil)
	hs.transcript.Reset()
	hs.transcript.Write([]byte{typeMessageHash, 0, 0, uint8(len(chHash))})
	hs.transcript.Write(chHash)

	helloRetryRequest := &serverHelloMsg{
		vers:              hs.hello.vers,
		random:            helloRetryRequestRandom,
		sessionId:         hs.hello.sessionId,
		cipherSuite:       hs.hello.cipherSuite,
		compressionMethod: hs.hello.compressionMethod,

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Client must send exactly one ClientHello and wait for ServerHello/HelloRetryRequest before sending further handshake messages
  2. Use a compliant TLS client library that respects handshake ordering
Defensive patterns

Strategy: try-catch

Try / catch

if err := tlsConn.Handshake(); err != nil {
    if strings.Contains(err.Error(), "handshake buffer not empty before HelloRetryRequest") {
        log.Printf("client sent extra messages before HRR from %v", remote)
    }
    c.Close()
    return
}

Prevention

When it happens

Trigger: Client sends a ClientHello followed immediately by another handshake message before waiting for the ServerHello/HelloRetryRequest.

Common situations: Buggy or non-compliant TLS clients, fuzzers, attackers probing handshake-state handling.

Understand the failure class

Related errors


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/6a3a54945b786e66. Report an issue: GitHub.