{"record":{"id":"2afdd29b0ccf968a","repo":"golang/go","slug":"tls-server-echoed-tls-1-3-compatibility-session-i","errorCode":null,"errorMessage":"tls: server echoed TLS 1.3 compatibility session ID in TLS 1.2","messagePattern":"tls: server echoed TLS 1\\.3 compatibility session ID in TLS 1\\.2","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"src/crypto/tls/handshake_client.go","lineNumber":547,"sourceCode":"\treturn nil\n}\n\n// Does the handshake, either a full one or resumes old session. Requires hs.c,\n// hs.hello, hs.serverHello, and, optionally, hs.session to be set.\nfunc (hs *clientHandshakeState) handshake() error {\n\tc := hs.c\n\n\t// If we did not load a session (hs.session == nil), but we did set a\n\t// session ID in the transmitted client hello (hs.hello.sessionId != nil),\n\t// it means we tried to negotiate TLS 1.3 and sent a random session ID as a\n\t// compatibility measure (see RFC 8446, Section 4.1.2).\n\t//\n\t// Since we're now handshaking for TLS 1.2, if the server echoed the\n\t// transmitted ID back to us, we know mischief is afoot: the session ID\n\t// was random and can't possibly be recognized by the server.\n\tif hs.session == nil && hs.hello.sessionId != nil && bytes.Equal(hs.hello.sessionId, hs.serverHello.sessionId) {\n\t\tc.sendAlert(alertIllegalParameter)\n\t\treturn errors.New(\"tls: server echoed TLS 1.3 compatibility session ID in TLS 1.2\")\n\t}\n\n\tisResume, err := hs.processServerHello()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ths.finishedHash = newFinishedHash(c.vers, hs.suite)\n\n\t// No signatures of the handshake are needed in a resumption.\n\t// Otherwise, in a full handshake, if we don't have any certificates\n\t// configured then we will never send a CertificateVerify message and\n\t// thus no signatures are needed in that case either.\n\tif isResume || (len(c.config.Certificates) == 0 && c.config.GetClientCertificate == nil) {\n\t\ths.finishedHash.discardHandshakeBuffer()\n\t}\n\n\tif err := transcriptMsg(hs.hello, &hs.finishedHash); err != nil {","sourceCodeStart":529,"sourceCodeEnd":565,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/crypto/tls/handshake_client.go#L529-L565","documentation":"In TLS 1.3 the client sends a random legacy session_id for middlebox compatibility (RFC 8446 4.1.2). If the handshake then negotiates TLS 1.2, a legitimate TLS 1.2 server must NOT echo that random ID because it cannot know it. hs.session == nil && hs.hello.sessionId != nil && bytes.Equal(hello.sessionId, serverHello.sessionId) means something in the path copied the TLS 1.3 compatibility ID into a TLS 1.2 ServerHello.","triggerScenarios":"A middlebox that does TLS 1.3 with the client but re-originates TLS 1.2 upstream while copying the legacy session_id field verbatim; a proxy that mirrors session_id bytes without understanding them.","commonSituations":"TLS-terminating load balancer, captive portal, or DLP appliance that mixes protocol-version semantics; rare for genuine servers.","solutions":["Identify and bypass or upgrade the middlebox that is replaying TLS 1.3 fields into a TLS 1.2 handshake.","Confirm by capturing traffic on both sides of the suspected appliance.","Treat as a potential interception indicator in security-sensitive deployments."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"func isCompatSessionIDEcho(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"server echoed TLS 1.3 compatibility session ID\")\n}","tryCatchPattern":"if _, err := tls.Dial(\"tcp\", addr, cfg); err != nil {\n    if isCompatSessionIDEcho(err) {\n        // Strong indicator of TLS interception; do not silently retry.\n        security.ReportInterception(addr, err)\n    }\n}","preventionTips":["Treat this as a security signal, not a transient failure.","Investigate the network path for middleboxes.","Prefer TLS 1.3 endpoints."],"tags":["tls","security","middlebox","handshake","protocol"],"backgroundTag":null,"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-12T12:31:55.035Z"}