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

  1. Bypass or upgrade the TLS-terminating middlebox / load balancer so TLS 1.3 passes through end-to-end.
  2. Verify the server's real capability with `openssl s_client -connect host:443 -tls1_3` from an unintercepted host.
  3. If you must restrict versions, set Config.MinVersion to what the path actually supports rather than blaming the server.
  4. 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

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

Related errors


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