golang/go · critical
tls: downgrade attempt detected, possibly due to a MitM atta
Error message
tls: downgrade attempt detected, possibly due to a MitM attack or a broken middlebox
What it means
Implements the downgrade sentinel mechanism from RFC 8446 4.1.3. When the client's maximum supported version is TLS 1.3 (or 1.2) but the negotiated version is lower, Go inspects the last 8 bytes of serverHello.random for canary values a legitimate higher-version server deliberately writes (downgradeCanaryTLS12 / downgradeCanaryTLS11). Their presence means the path forced a version lower than the server can actually speak.
Source
Thrown at src/crypto/tls/handshake_client.go:325
if !ok {
c.sendAlert(alertUnexpectedMessage)
return unexpectedMessageError(serverHello, msg)
}
if err := c.pickTLSVersion(serverHello); err != nil {
return err
}
// If we are negotiating a protocol version that's lower than what we
// support, check for the server downgrade canaries.
// See RFC 8446, Section 4.1.3.
maxVers := c.config.maxSupportedVersion(roleClient, c.quic != nil)
tls12Downgrade := string(serverHello.random[24:]) == downgradeCanaryTLS12
tls11Downgrade := string(serverHello.random[24:]) == downgradeCanaryTLS11
if maxVers == VersionTLS13 && c.vers <= VersionTLS12 && (tls12Downgrade || tls11Downgrade) ||
maxVers == VersionTLS12 && c.vers <= VersionTLS11 && tls11Downgrade {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: downgrade attempt detected, possibly due to a MitM attack or a broken middlebox")
}
if c.vers == VersionTLS13 {
hs := &clientHandshakeStateTLS13{
c: c,
ctx: ctx,
serverHello: serverHello,
hello: hello,
keyShareKeys: keyShareKeys,
session: session,
earlySecret: earlySecret,
binderKey: binderKey,
echContext: ech,
}
return hs.handshake()
}
hs := &clientHandshakeState{View on GitHub (pinned to b6b368adc5)
Solutions
- Bypass or upgrade the TLS-terminating middlebox / load balancer so TLS 1.3 passes through end-to-end.
- Verify the server's real capability with `openssl s_client -connect host:443 -tls1_3` from an unintercepted host.
- If you must restrict versions, set Config.MinVersion to what the path actually supports rather than blaming the server.
- If nothing is intercepting, treat this as a security incident and investigate the path.
Example fix
// before: pinning MaxVersion can force the downgrade path
cfg := &tls.Config{MaxVersion: tls.VersionTLS12}
// after: allow TLS 1.3 and let the server negotiate honestly
cfg := &tls.Config{MinVersion: tls.VersionTLS12} Defensive patterns
Strategy: try-catch
Validate before calling
// You cannot pre-validate a downgrade sentinel without performing the handshake.
// You can constrain the version policy:
func saneVersionPolicy(cfg *tls.Config) {
if cfg.MinVersion == 0 { cfg.MinVersion = tls.VersionTLS12 }
// Do not force a MaxVersion below the server's capability.
} Type guard
func isDowngradeDetected(err error) bool {
return err != nil && strings.Contains(err.Error(), "downgrade attempt detected")
} Try / catch
if _, err := tls.Dial("tcp", addr, cfg); err != nil {
if isDowngradeDetected(err) {
// Log a security incident; the path is intercepting or downgrading.
log.Printf("SECURITY: TLS downgrade detected to %s: %v", addr, err)
}
} Prevention
- Do not set Config.MaxVersion below what the server supports.
- Audit the network path for TLS-intercepting appliances.
- Prefer TLS 1.3 (MinVersion = VersionTLS13).
When it happens
Trigger: Client supports TLS 1.3 but ends up negotiating TLS 1.2 while the server's random contains the 444F574E47724431 (DOWNGRD1) sentinel; or TLS 1.2 negotiated against a TLS 1.2-capable server while the TLS 1.1 canary is present. Occurs when a TLS-intercepting middlebox, broken load balancer, or MitM strips ServerHello supported_versions.
Common situations: Corporate TLS inspection / DLP appliance in the path; legacy reverse proxy that terminates TLS 1.2 only; misconfigured ingress that downgrades; genuine MitM attack.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- tls: invalid signature by the server certificate: {err}
- tls: invalid server finished hash
- tls: client using inappropriate protocol fallback
- tls: invalid outer extensions
- tls: malformed encrypted_client_hello extension
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/cc891c20fb914228.
Report an issue: GitHub.