{"record":{"id":"c641d0560bb2435f","repo":"golang/go","slug":"tls-second-client-hello-missing-encrypted-client","errorCode":null,"errorMessage":"tls: second client hello missing encrypted client hello extension","messagePattern":"tls: second client hello missing encrypted client hello extension","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/crypto/tls/handshake_server_tls13.go","lineNumber":596,"sourceCode":"\t\treturn nil, err\n\t}\n\n\t// clientHelloMsg is not included in the transcript.\n\tmsg, err := c.readHandshake(nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tclientHello, ok := msg.(*clientHelloMsg)\n\tif !ok {\n\t\tc.sendAlert(alertUnexpectedMessage)\n\t\treturn nil, unexpectedMessageError(clientHello, msg)\n\t}\n\n\tif hs.echContext != nil {\n\t\tif len(clientHello.encryptedClientHello) == 0 {\n\t\t\tc.sendAlert(alertMissingExtension)\n\t\t\treturn nil, errors.New(\"tls: second client hello missing encrypted client hello extension\")\n\t\t}\n\n\t\techType, echCiphersuite, configID, encap, payload, err := parseECHExt(clientHello.encryptedClientHello)\n\t\tif err != nil {\n\t\t\tc.sendAlert(alertDecodeError)\n\t\t\treturn nil, errors.New(\"tls: client sent invalid encrypted client hello extension\")\n\t\t}\n\n\t\tif echType == outerECHExt && hs.echContext.inner || echType == innerECHExt && !hs.echContext.inner {\n\t\t\tc.sendAlert(alertDecodeError)\n\t\t\treturn nil, errors.New(\"tls: unexpected switch in encrypted client hello extension type\")\n\t\t}\n\n\t\tif echType == outerECHExt {\n\t\t\tif echCiphersuite != hs.echContext.ciphersuite || configID != hs.echContext.configID || len(encap) != 0 {\n\t\t\t\tc.sendAlert(alertIllegalParameter)\n\t\t\t\treturn nil, errors.New(\"tls: second client hello encrypted client hello extension does not match\")\n\t\t\t}","sourceCodeStart":578,"sourceCodeEnd":614,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/crypto/tls/handshake_server_tls13.go#L578-L614","documentation":"When ECH (Encrypted Client Hello) was offered in the first ClientHello, the second ClientHello (after HelloRetryRequest) must also carry the ECH extension. If hs.echContext != nil but clientHello.encryptedClientHello is empty, the server sends missing_extension.","triggerScenarios":"Client offers ECH in ClientHello1, receives HRR, but omits the ECH extension in ClientHello2.","commonSituations":"Buggy ECH client that drops the extension on retry; partial ECH support; ECH draft-revision drift between client and server.","solutions":["Ensure ClientHello2 includes the ECH extension consistent with the HRR's ECH configuration","Update the ECH-capable client library to a version that retries ECH correctly"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := tlsConn.Handshake(); err != nil {\n    if strings.Contains(err.Error(), \"second client hello missing encrypted client hello\") {\n        log.Printf(\"ECH dropped on retry from %v\", remote)\n    }\n    c.Close()\n    return\n}","preventionTips":["ECH clients must resend the ECH extension in ClientHello2 after HelloRetryRequest","Use an ECH library that correctly handles the HRR retry flow"],"tags":["tls","go","ech","encrypted-client-hello","hello-retry-request","handshake"],"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-12T06:17:24.410Z"}