{"record":{"id":"f61286b19b2e46d7","repo":"golang/go","slug":"tls-received-a-session-ticket-with-empty-opaque-t","errorCode":null,"errorMessage":"tls: received a session ticket with empty opaque ticket label","messagePattern":"tls: received a session ticket with empty opaque ticket label","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/crypto/tls/handshake_client_tls13.go","lineNumber":859,"sourceCode":"\t}\n\n\tif c.config.SessionTicketsDisabled || c.config.ClientSessionCache == nil {\n\t\treturn nil\n\t}\n\n\t// See RFC 8446, Section 4.6.1.\n\tif msg.lifetime == 0 {\n\t\treturn nil\n\t}\n\tlifetime := time.Duration(msg.lifetime) * time.Second\n\tif lifetime > maxSessionTicketLifetime {\n\t\tc.sendAlert(alertIllegalParameter)\n\t\treturn errors.New(\"tls: received a session ticket with invalid lifetime\")\n\t}\n\n\tif len(msg.label) == 0 {\n\t\tc.sendAlert(alertDecodeError)\n\t\treturn errors.New(\"tls: received a session ticket with empty opaque ticket label\")\n\t}\n\n\t// RFC 9001, Section 4.6.1\n\tif c.quic != nil && msg.maxEarlyData != 0 && msg.maxEarlyData != 0xffffffff {\n\t\tc.sendAlert(alertIllegalParameter)\n\t\treturn errors.New(\"tls: invalid early data for QUIC connection\")\n\t}\n\n\tcipherSuite := cipherSuiteTLS13ByID(c.cipherSuite)\n\tif cipherSuite == nil || c.resumptionSecret == nil {\n\t\treturn c.sendAlert(alertInternalError)\n\t}\n\n\tpsk := tls13.ExpandLabel(cipherSuite.hash.New, c.resumptionSecret, \"resumption\",\n\t\tmsg.nonce, cipherSuite.hash.Size())\n\n\tsession := c.sessionState()\n\tsession.secret = psk","sourceCodeStart":841,"sourceCodeEnd":877,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/crypto/tls/handshake_client_tls13.go#L841-L877","documentation":"A TLS 1.3 client received a NewSessionTicket whose ticket field (called label internally) is empty. RFC 8446 requires the ticket to be a non-empty opaque vector; an empty value is a decode error and cannot identify a resumable session.","triggerScenarios":"handleNewSessionTicket checks len(msg.label) == 0 after the lifetime check; if true, it sends alertDecodeError. The peer transmitted a newSessionTicketMsgTLS13 with an empty ticket body.","commonSituations":"A server bug generating zero-length tickets (e.g. a ticket encryptor with an empty key, a misconfigured session cache, or a stub implementation). Also seen in fuzz tests and malformed-protocol test suites.","solutions":["Report to the server operator — RFC 8446 §4.6.1 forbids empty ticket values.","If you operate the server, verify the session ticket key/seeding logic produces non-empty ciphertext.","Disable session tickets on the client (SessionTicketsDisabled: true or omit ClientSessionCache) to tolerate the broken peer if resumption is not required.","File an upstream issue against the offending TLS implementation."],"exampleFix":"// client-side workaround (only if peer cannot be fixed)\ncfg := &tls.Config{\n    SessionTicketsDisabled: true,\n}","handlingStrategy":"validation","validationCode":"// Server-side: ensure ticket bytes are non-empty before sending.\nif len(ticket.Label) == 0 {\n    return errors.New(\"refusing to send empty session ticket\")\n}","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"empty opaque ticket label\") {\n    // disable resumption against this server or file an upstream bug\n    cfg.SessionTicketsDisabled = true\n}","preventionTips":["Never issue tickets with empty ticket fields.","Test ticket generation with empty-key guard rails.","Disable ClientSessionCache if a peer is known-broken and resumption is optional."],"tags":["tls","client-handshake","session-ticket","protocol-violation","tls13"],"backgroundTag":null,"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}