VYPR
High severity7.8GHSA Advisory· Published Jun 16, 2026· Updated Jun 16, 2026

Traefik: HTTP/3 mTLS bypass via exact SNI TLSOptions lookup for wildcard and mixed-case hosts

CVE-2026-53622

Description

Traefik HTTP/3 mTLS bypass: exact SNI lookup fails on wildcard/mixed-case hosts, letting attackers skip client certificate authentication.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Traefik HTTP/3 mTLS bypass: exact SNI lookup fails on wildcard/mixed-case hosts, letting attackers skip client certificate authentication.

Vulnerability

A critical vulnerability in Traefik's HTTP/3 (QUIC) TLS configuration selection, present in versions v3.7.0 and v3.7.1, allows unauthenticated clients to bypass router-specific mTLS enforcement [1][2]. When HTTP/3 is enabled on an entrypoint, the QUIC handshake selects the TLS configuration via Router.GetTLSGetClientInfo(), which performs a direct, case-sensitive map lookup on the SNI value (hostHTTPTLSConfig[info.ServerName]). This lookup fails to match wildcard host patterns (e.g., *.example.com) or case variants of the configured hostname, causing the handshake to fall back to the default TLS configuration — which often does not require client certificates. The subsequent HTTP routing layer still dispatches the request to a backend protected by a router-specific mTLS policy, creating the bypass [1][2].

Exploitation

An attacker needs only UDP network access to a Traefik entrypoint where HTTP/3 is enabled and a router uses a wildcard Host rule or case-insensitive hostname matching, combined with a router-specific TLSOptions that enforces client certificate authentication [1][2]. Two exploit paths are confirmed [1][2]: - Wildcard bypass: Sending an HTTP/3 request with any SNI matching a wildcard rule (e.g., *.example.com) allows the handshake to use the default TLS config, while the HTTP layer still routes to the protected backend. - Mixed-case bypass: For an exact host rule (e.g., api.example.com), sending an SNI with mixed case (e.g., API.EXAMPLE.COM) causes the lookup to miss, falling back to default TLS, yet the HTTP layer matches the host case-insensitively and routes the request to the protected backend. The attacker completes the QUIC handshake without presenting any client certificate.

Impact

On successful exploitation, an attacker can reach a backend that expects mutual TLS authentication without providing a client certificate, effectively bypassing mTLS enforcement for the affected routes [1][2]. The compromise of confidentiality, integrity, or availability depends on the backend service's sensitivity; however, the primary impact is the complete circumvention of client authentication, potentially allowing unauthorized access to protected resources.

Mitigation

A fix has been released in Traefik version v3.7.3 [3]. The advisory strongly recommends upgrading to this version immediately [1][2]. No workarounds are documented in the available references; disabling HTTP/3 on affected entrypoints may serve as a temporary mitigation until the patch is applied.

AI Insight generated on Jun 16, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

1

Patches

2
5ea71f1c3af0

Prepare release v3.7.3

https://github.com/traefik/traefikRomainJun 4, 2026Fixed in 3.7.3via ghsa-release-walk
3 files changed · +15 9
  • CHANGELOG.md+8 2 modified
    @@ -1,7 +1,8 @@
    -## [v3.7.2](https://github.com/traefik/traefik/tree/v3.7.2) (2026-06-03)
    -[All Commits](https://github.com/traefik/traefik/compare/v3.7.1...v3.7.2)
    +## [v3.7.3](https://github.com/traefik/traefik/tree/v3.7.3) (2026-06-04)
    +[All Commits](https://github.com/traefik/traefik/compare/v3.7.1...v3.7.3)
     
     **Bug fixes:**
    +- **[tls]** Compute resolved tlsOptions after applying models ([#13291](https://github.com/traefik/traefik/pull/13291) @rtribotte)
     - **[webui, tcp]** Fix TCP router service resolution in dashboard flow diagram ([#13155](https://github.com/traefik/traefik/pull/13155) @aliamerj)
     - **[k8s/ingress-nginx]** Trim quotes from proxy_set_header header name ([#13203](https://github.com/traefik/traefik/pull/13203) @crisbal)
     - **[accesslogs]** Escape double quotes in quoted log fields ([#13180](https://github.com/traefik/traefik/pull/13180) @KaanSimsek)
    @@ -70,6 +71,11 @@
     - **[middleware]** Reject requests with different paths after StripPrefix and StripPrefixRegex normalisation ([#13215](https://github.com/traefik/traefik/pull/13215) @rtribotte)
     - **[server]** Bump golang.org/x/net to v0.55.0 ([#13251](https://github.com/traefik/traefik/pull/13251) @kevinpollet)
     - **[server]** Bump golang.org/x/crypto to v0.52.0 ([#13276](https://github.com/traefik/traefik/pull/13276) @rtribotte)
    +- 
    +## [v3.7.2](https://github.com/traefik/traefik/tree/v3.7.2) (2026-06-03)
    +[All Commits](https://github.com/traefik/traefik/compare/v3.7.1...v3.7.2)
    +
    +Release canceled.
     
     ## [v3.6.18](https://github.com/traefik/traefik/tree/v3.6.18) (2026-06-03)
     [All Commits](https://github.com/traefik/traefik/compare/v3.6.17...v3.6.18)
    
  • docs/content/migrate/v3.md+4 4 modified
    @@ -9,11 +9,11 @@ This guide provides detailed migration steps for upgrading between different Tra
     
     ---
     
    -## v3.7.2
    +## v3.7.3
     
     ### Kubernetes Gateway API Provider
     
    -Starting with `v3.7.2`, the QPS and Burst values of the Kubernetes client used by the Kubernetes Gateway API provider have been increased to `50` and `100` respectively (10x the default values of the Kubernetes client).
    +Starting with `v3.7.3`, the QPS and Burst values of the Kubernetes client used by the Kubernetes Gateway API provider have been increased to `50` and `100` respectively (10x the default values of the Kubernetes client).
     
     The Kubernetes Gateway API provider writes status updates intensively to comply with the Kubernetes Gateway API specification.
     This change helps avoid performance issues related to Kubernetes API rate limiting, which can increase the setup time when a new routing configuration is built.
    @@ -23,13 +23,13 @@ and [`kubernetesGateway.burst`](../reference/install-configuration/providers/kub
     
     ### BasicAuth Middleware
     
    -From version `v3.7.2` onwards, the BasicAuth middleware requires a non-empty users configuration in order to be built successfully.
    +From version `v3.7.3` onwards, the BasicAuth middleware requires a non-empty users configuration in order to be built successfully.
     Previously, the middleware would be built successfully but always return a 401 status code for any request.
     Now, an error occurs and any routers using it will be unmounted. For the same request, a 404 status code is served instead of a 401 status code.
     
     ### StripPrefix and StripPrefixRegex Middleware
     
    -From version `v3.7.2` onwards, the StripPrefix middleware and the StripPrefixRegex middleware reject requests (`400 Bad Request`)
    +From version `v3.7.3` onwards, the StripPrefix middleware and the StripPrefixRegex middleware reject requests (`400 Bad Request`)
     when stripping the configured prefix produces a path that differs from its normalised form
     (i.e. a path containing `.` or `..` segments that would be collapsed by normalisation).
     
    
  • script/gcg/traefik-bugfix.toml+3 3 modified
    @@ -4,11 +4,11 @@ RepositoryName = "traefik"
     OutputType = "file"
     FileName = "traefik_changelog.md"
     
    -# example new bugfix v3.7.2
    +# example new bugfix v3.7.3
     CurrentRef = "v3.7"
    -PreviousRef = "v3.7.1"
    +PreviousRef = "v3.7.2"
     BaseBranch = "v3.7"
    -FutureCurrentRefName = "v3.7.2"
    +FutureCurrentRefName = "v3.7.3"
     
     ThresholdPreviousRef = 10000
     ThresholdCurrentRef = 10000
    
a664812e9c30

Compute resolved tlsOptions after applying models

https://github.com/traefik/traefikRomainJun 4, 2026Fixed in 3.7.3via ghsa-release-walk
5 files changed · +166 2
  • integration/fixtures/https/https_entrypoint_tls.toml+53 0 added
    @@ -0,0 +1,53 @@
    +[global]
    +  checkNewVersion = false
    +  sendAnonymousUsage = false
    +
    +[log]
    +  level = "DEBUG"
    +
    +[entryPoints]
    +  [entryPoints.websecure]
    +    address = ":4443"
    +    [entryPoints.websecure.http.tls]
    +
    +  [entryPoints.websecure-options]
    +    address = ":4444"
    +    [entryPoints.websecure-options.http.tls]
    +      options = "foo"
    +
    +[api]
    +  insecure = true
    +
    +[providers.file]
    +  filename = "{{ .SelfFilename }}"
    +
    +## dynamic configuration ##
    +
    +[http.routers]
    +  [http.routers.router1]
    +    entryPoints = ["websecure"]
    +    service = "service1"
    +    rule = "Host(`snitest.com`)"
    +
    +  [http.routers.router2]
    +    entryPoints = ["websecure-options"]
    +    service = "service1"
    +    rule = "Host(`snitest.org`)"
    +
    +[http.services]
    +  [http.services.service1]
    +    [http.services.service1.loadBalancer]
    +      [[http.services.service1.loadBalancer.servers]]
    +        url = "http://127.0.0.1:9010"
    +
    +[[tls.certificates]]
    +  certFile = "fixtures/https/snitest.com.cert"
    +  keyFile = "fixtures/https/snitest.com.key"
    +
    +[[tls.certificates]]
    +  certFile = "fixtures/https/snitest.org.cert"
    +  keyFile = "fixtures/https/snitest.org.key"
    +
    +[tls.options]
    +  [tls.options.foo]
    +    maxVersion = "VersionTLS12"
    
  • integration/https_test.go+62 1 modified
    @@ -114,8 +114,69 @@ func (s *HTTPSSuite) TestWithSNIConfigRoute() {
     	require.NoError(s.T(), err)
     }
     
    -// TestWithTLSOptions  verifies that traefik routes the requests with the associated tls options.
    +// TestWithEntryPointTLSConfig verifies that a router relying on the entry point
    +// TLS configuration (without an explicit router TLS section) is served over HTTPS,
    +// including when the entry point references user-defined TLS options.
    +// Regression test for https://github.com/traefik/traefik/issues/13289.
    +func (s *HTTPSSuite) TestWithEntryPointTLSConfig() {
    +	file := s.adaptFile("fixtures/https/https_entrypoint_tls.toml", struct{}{})
    +	s.traefikCmd(withConfigFile(file))
    +
    +	// wait for Traefik
    +	err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`snitest.com`)"))
    +	require.NoError(s.T(), err)
    +
    +	backend := startTestServer("9010", http.StatusNoContent, "")
    +	defer backend.Close()
    +
    +	err = try.GetRequest(backend.URL, 1*time.Second, try.StatusCodeIs(http.StatusNoContent))
    +	require.NoError(s.T(), err)
    +
    +	tr := &http.Transport{
    +		TLSClientConfig: &tls.Config{
    +			InsecureSkipVerify: true,
    +			ServerName:         "snitest.com",
    +		},
    +	}
    +
    +	req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
    +	require.NoError(s.T(), err)
    +	req.Host = tr.TLSClientConfig.ServerName
    +	req.Header.Set("Host", tr.TLSClientConfig.ServerName)
    +	req.Header.Set("Accept", "*/*")
    +
    +	err = try.RequestWithTransport(req, 30*time.Second, tr, try.HasCn(tr.TLSClientConfig.ServerName), try.StatusCodeIs(http.StatusNoContent))
    +	require.NoError(s.T(), err)
    +
    +	// The websecure-options entry point references the user-defined "foo" TLS options (maxVersion VersionTLS12).
    +	// A request with no router-level TLS must still have these options resolved and applied.
    +	trOptions := &http.Transport{
    +		TLSClientConfig: &tls.Config{
    +			InsecureSkipVerify: true,
    +			ServerName:         "snitest.org",
    +		},
    +	}
    +
    +	req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4444/", nil)
    +	require.NoError(s.T(), err)
    +	req.Host = trOptions.TLSClientConfig.ServerName
    +	req.Header.Set("Host", trOptions.TLSClientConfig.ServerName)
    +	req.Header.Set("Accept", "*/*")
    +
    +	err = try.RequestWithTransport(req, 30*time.Second, trOptions, try.HasCn(trOptions.TLSClientConfig.ServerName), try.StatusCodeIs(http.StatusNoContent))
    +	require.NoError(s.T(), err)
    +
    +	// A TLS 1.3-only client must fail the handshake, proving the "foo" options
    +	// (resolved from the entry point) are effectively enforced.
    +	_, err = tls.Dial("tcp", "127.0.0.1:4444", &tls.Config{
    +		InsecureSkipVerify: true,
    +		ServerName:         "snitest.org",
    +		MinVersion:         tls.VersionTLS13,
    +	})
    +	assert.Error(s.T(), err)
    +}
     
    +// TestWithTLSOptions verifies that traefik routes the requests with the associated tls options.
     func (s *HTTPSSuite) TestWithTLSOptions() {
     	file := s.adaptFile("fixtures/https/https_tls_options.toml", struct{}{})
     	s.traefikCmd(withConfigFile(file))
    
  • pkg/server/aggregator.go+1 1 modified
    @@ -138,7 +138,7 @@ func mergeConfiguration(configurations dynamic.Configurations, defaultEntryPoint
     		delete(conf.TLS.Options, traefiktls.DefaultTLSConfigName)
     	}
     
    -	return resolveHTTPTLSOptions(conf)
    +	return conf
     }
     
     func resolveHTTPTLSOptions(cfg dynamic.Configuration) dynamic.Configuration {
    
  • pkg/server/configurationwatcher.go+1 0 modified
    @@ -167,6 +167,7 @@ func (c *ConfigurationWatcher) applyConfigurations(ctx context.Context) {
     
     			conf := mergeConfiguration(newConfigs.DeepCopy(), c.defaultEntryPoints)
     			conf = applyModel(conf)
    +			conf = resolveHTTPTLSOptions(conf)
     
     			for _, listener := range c.configurationListeners {
     				listener(conf)
    
  • pkg/server/configurationwatcher_test.go+49 0 modified
    @@ -893,3 +893,52 @@ func TestPublishConfigUpdatedByConfigWatcherListener(t *testing.T) {
     
     	assert.Equal(t, 1, publishedConfigCount)
     }
    +
    +// TestEntryPointTLSResolvedOptions is a regression test for
    +// https://github.com/traefik/traefik/issues/13289: a router whose TLS
    +// configuration comes from the entry point (and not from an explicit router TLS
    +// section) must still have its TLS options resolved in the published configuration.
    +func TestEntryPointTLSResolvedOptions(t *testing.T) {
    +	routinesPool := safe.NewPool(t.Context())
    +	t.Cleanup(routinesPool.Stop)
    +
    +	pvd := &mockProvider{
    +		messages: []dynamic.Message{{
    +			ProviderName: "internal",
    +			Configuration: &dynamic.Configuration{
    +				HTTP: &dynamic.HTTPConfiguration{
    +					Routers: map[string]*dynamic.Router{
    +						"foo": {
    +							EntryPoints: []string{"websecure"},
    +							Rule:        "Host(`foo.example.com`)",
    +							Service:     "service",
    +						},
    +					},
    +					Models: map[string]*dynamic.Model{
    +						"websecure": {
    +							TLS: &dynamic.RouterTLSConfig{},
    +						},
    +					},
    +				},
    +			},
    +		}},
    +	}
    +
    +	watcher := NewConfigurationWatcher(routinesPool, pvd, []string{}, "")
    +
    +	run := make(chan struct{})
    +	watcher.AddListener(func(conf dynamic.Configuration) {
    +		router := conf.HTTP.Routers["foo@internal"]
    +		if router == nil || router.TLS == nil {
    +			return
    +		}
    +
    +		assert.Equal(t, "default", router.TLS.ResolvedOptions)
    +		close(run)
    +	})
    +
    +	watcher.Start()
    +	t.Cleanup(watcher.Stop)
    +
    +	<-run
    +}
    

Vulnerability mechanics

Root cause

"Exact, case-sensitive map lookup on SNI in the HTTP/3 TLS callback fails to match wildcard or mixed-case hostnames, causing fallback to the default TLS configuration and bypassing router-specific mTLS."

Attack vector

An unauthenticated attacker with UDP access to the HTTP/3 entrypoint sends a QUIC ClientHello with an SNI value that either matches a wildcard pattern (e.g., `api.example.com` when the router uses `Host(\`*.example.com\`)`) or uses mixed case (e.g., `API.EXAMPLE.COM` when the router uses `Host(\`api.example.com\`)`). Because `GetTLSGetClientInfo()` performs an exact, case-sensitive map lookup, the SNI does not match any router-specific TLS configuration, so the handshake falls back to the default TLS config—which typically does not require a client certificate. The QUIC handshake succeeds without mTLS, and the subsequent HTTP routing layer still dispatches the request to the backend protected by the router-specific mTLS policy [ref_id=1][ref_id=2].

Affected code

The vulnerability resides in the HTTP/3 TLS configuration selection path. In `pkg/server/server_entrypoint_tcp_http3.go`, the QUIC TLS callback is wired to `rt.GetTLSGetClientInfo()` from `pkg/server/router/tcp/router.go`, which performs an exact, case-sensitive map lookup on `hostHTTPTLSConfig[info.ServerName]`. This fails to match wildcard patterns (e.g., `*.example.com`) or case variants of the configured hostname, causing the handshake to fall back to the default TLS configuration.

What the fix does

The patch moves the `resolveHTTPTLSOptions` call from inside `mergeConfiguration` to after `applyModel` in `configurationwatcher.go` [patch_id=6214999]. Previously, TLS options were resolved before entry-point models were applied, so routers that inherited their TLS configuration from an entry-point model never had their `ResolvedOptions` field populated. By resolving TLS options after models are applied, the fix ensures that the correct TLS configuration (including mTLS options) is associated with the router and available during the HTTP/3 TLS handshake, preventing the fallback to the default config.

Preconditions

  • configHTTP/3 is enabled on the affected entrypoint
  • configA router-specific TLSOptions configuration enforces client certificate authentication
  • configThe default/fallback TLS configuration does not require client certificates
  • networkUDP access to the HTTP/3 entrypoint is reachable by the attacker

Generated on Jun 16, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

3

News mentions

0

No linked articles in our index yet.