CVE-2026-88007 — traefik
Fix: traefik/traefik@ff39c47CVE-2026-88007 is a Improper Authentication vulnerability in github.com/traefik/traefik/v3. A fix is available for github.com/traefik/traefik/v3 — see the affected versions and patch details below.
Traefik HTTP/3 Backend NTLM Connection Reuse
Exploitation Status
No confirmed exploitation observed yet
- CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.
- A successful exploit gives an attacker total control of the affected component, not partial access.
- CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.
Exploitation and automatability from CISA’s SSVC triage for CVE-2026-88007.
EPSS Exploitation Probability
Probability of exploitation in the next 30 days, from FIRST.org EPSS.
Real-World Exposure
github.com/traefik/traefik/v3🐹github.com/traefik/traefik/v2Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.
Description
Summary
Traefik's HTTP/3 request path did not initialize the connection-scoped backend transport holder that isolates connection-bound NTLM and Negotiate (Kerberos) authentication on the HTTP/1.1 and HTTP/2 paths. The HTTP/3 entrypoint reuses the HTTPS handler chain and reaches the same backend round-tripper, but its ConnContext never called service.AddTransportOnContext, so kerberosRoundTripper fell back to the shared backend transport instead of a per-frontend-connection pool. On a route served over HTTP/3 to a backend that binds identity to a persistent connection via NTLM or Negotiate, an unrelated HTTP/3 client could be assigned a backend connection already authenticated as a victim and inherit that identity, reading victim-only data and performing actions as the victim without presenting the victim's credentials. Affected deployments require HTTP/3 enabled on the entrypoint, a backend using connection-bound NTLM/Negotiate authentication, and backend keep-alive; deployments using ordinary per-request authentication are not affected.
Patches
- https://github.com/traefik/traefik/releases/tag/v2.11.57
- https://github.com/traefik/traefik/releases/tag/v3.7.13
For more information
If you have any questions or comments about this advisory, please open an issue.
<details> <summary>Original Description</summary>Traefik HTTP/3 Backend NTLM Connection Reuse
Summary
Traefik's HTTP/3 request path does not initialize the connection-scoped backend transport state that Traefik uses to isolate connection-bound NTLM and Negotiate authentication for HTTP/1.1 and HTTP/2. When a backend keeps authenticated identity on a persistent HTTP/1.1 TCP connection, an unrelated HTTP/3 client can reuse a victim-authenticated backend connection and inherit that backend identity.
In the attached reproduction, the HTTPS/HTTP/1.1 control case behaves correctly and isolates the attacker, but the HTTP/3 case allows a second unauthenticated client to read victim-only data and execute a state-changing request as actor=victim.
Validated target:
- Repository:
traefik/traefik - Commit:
f2d0794417e4d06343e6e7c4722143f5b34bee45 - Validation time:
2026-08-25T06:48:02Z - Commit time:
2026-08-24T08:26:06Z - Patched status: not evaluated
Details
The issue is caused by a protocol-parity gap between the normal TCP HTTP entrypoint path and the HTTP/3 entrypoint path.
For HTTP/1.1 and HTTP/2, Traefik explicitly creates a connection-scoped holder that can later store a dedicated RoundTripper for NTLM or Negotiate:
// pkg/server/server_entrypoint_tcp.go:691-703
var connContext multipleConnContext
connContext.AddConnContextFunc(func(ctx context.Context, c net.Conn) context.Context {
// This adds an empty struct in order to store a RoundTripper in the ConnContext in case of Kerberos or NTLM.
ctx = service.AddTransportOnContext(ctx)
if tlsConn, ok := c.(*tls.Conn); ok {
if tlsConnWithOptionsName, ok := tlsConn.NetConn().(tcp.TLSConn); ok {
return tcp.AddTLSOptionsNameInContext(ctx, tlsConnWithOptionsName.TLSOptionsName)
}
}
return ctx
})
That helper installs the per-connection holder, and kerberosRoundTripper depends on it. If the holder is absent, it falls back to the shared original backend transport. If NTLM or Negotiate is detected, it stores a dedicated cloned RoundTripper into that holder so future requests stay on the authenticated backend connection:
// pkg/server/service/transport.go:374-402
func AddTransportOnContext(ctx context.Context) context.Context {
return context.WithValue(ctx, transportKey, &stickyRoundTripper{})
}
type kerberosRoundTripper struct {
new func() http.RoundTripper
OriginalRoundTripper http.RoundTripper
}
func (k *kerberosRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
value, ok := request.Context().Value(transportKey).(*stickyRoundTripper)
if !ok {
return k.OriginalRoundTripper.RoundTrip(request)
}
if value.RoundTripper != nil {
return value.RoundTripper.RoundTrip(request)
}
resp, err := k.OriginalRoundTripper.RoundTrip(request)
// If we found that we are authenticating with Kerberos (Negotiate) or NTLM.
// We put a dedicated roundTripper in the ConnContext.
// This will stick the next calls to the same connection with the backend.
if err == nil && containsNTLMorNegotiate(resp.Header.Values("WWW-Authenticate")) {
value.RoundTripper = k.new()
}
return resp, err
}
For HTTP/3, the server reuses the normal HTTPS handler chain, but its ConnContext only propagates the TLS options name and does not call service.AddTransportOnContext:
// pkg/server/server_entrypoint_tcp_http3.go:65-80
h3.Server = &http3.Server{
Addr: config.GetAddress(),
Port: config.HTTP3.AdvertisedPort,
Handler: httpsServer.Server.(*http.Server).Handler,
TLSConfig: &tls.Config{GetConfigForClient: h3.getTLSConfigForClient},
QUICConfig: &quic.Config{
Allow0RTT: false,
},
ConnContext: func(ctx context.Context, c *quic.Conn) context.Context {
tlsOptionsName, err := h3.getTLSOptionsName(c)
if err != nil {
log.Error().Msgf("Error getting TLS options name for client: %v", err)
return ctx
}
return tcp.AddTLSOptionsNameInContext(ctx, tlsOptionsName)
},
}
This means HTTP/3 requests reach the same reverse-proxy and backend transport logic as HTTPS, but without the connection-scoped transport holder that NTLM and Negotiate isolation relies on.
In practice, the flow is:
- A victim authenticates through Traefik to a backend that binds identity to the backend TCP connection using NTLM or Negotiate.
- Because the HTTP/3 request context does not contain
transportKey,kerberosRoundTripperuses the sharedOriginalRoundTripper. - No frontend-connection-specific dedicated backend pool is installed for that HTTP/3 client.
- A second unrelated HTTP/3 client can be assigned the same backend TCP connection after the victim has authenticated it.
- That second client inherits the victim's backend identity without sending the victim's credentials.
The attached verifier demonstrates both the negative control and the exploit path:
- HTTPS/HTTP/1.1 control case: the attacker uses a separate frontend connection and correctly receives
401 - HTTP/3 exploit case: the attacker uses a separate HTTP/3 client with no
Authorizationheader, readsresource=secret actor=victim, executesaction=transfer actor=victim to=attacker amount=5000, and hits the same backend TCP connection identifier as the victim
PoC
See the reproduction materials at: https://gist.github.com/OneZ3r0/41da8e8b79ebbe444a94f8a2a3a30895
The gist can also be downloaded as a ZIP archive.
Files included in this gist:
run.shDockerfile.dockerignorego.modgo.sumverify.go
The package is intentionally kept as a single-container reproduction:
run.shbuilds a local image for the pinned target commit- the Dockerfile builds both Traefik and the verifier during image build
- the container runs the verifier directly as its entrypoint
- the verifier starts a synthetic backend, launches Traefik, runs the HTTPS/HTTP/1.1 control case, then runs the HTTP/3 exploit case
Run:
./run.sh
run.sh defaults to the validated commit above. To override it explicitly:
PRODUCT_COMMIT=f2d0794417e4d06343e6e7c4722143f5b34bee45 ./run.sh
Expected terminal result:
REPRODUCED: HTTP/1.1 isolates the authenticated backend connection, but HTTP/3 reuses the victim-authenticated backend connection for a different client and executes an unauthorized state-changing request as the victim.
Important observed behavior from the PoC:
- the HTTP/1.1 control case succeeds only if a fresh attacker connection receives
401 - the HTTP/3 exploit case succeeds only if the attacker reads victim-only data without sending
Authorization - the HTTP/3 exploit case succeeds only if the attacker performs
/transfer?to=attacker&amount=5000asactor=victim - the HTTP/3 exploit case succeeds only if the attacker uses the same backend TCP connection identifier as the victim
Environment notes:
- Docker is required
- the build fetches the target Traefik source from GitHub
- no production credentials or external NTLM service are required; the verifier includes a synthetic NTLM-like backend specifically to demonstrate connection-bound identity reuse
Impact
This is a cross-client authorization bypass affecting deployments that expose HTTP/3 routes to backends using connection-bound NTLM or Negotiate authentication with persistent backend connection reuse.
In the verified reproduction, an unauthenticated second client can:
- read victim-only data
- perform a state-changing action as the victim
- reuse a backend TCP connection that has already been authenticated as the victim
Attack prerequisites:
- HTTP/3 enabled on the Traefik entrypoint
- a routed backend using connection-bound NTLM or Negotiate authentication
- backend keep-alive and backend connection reuse enabled
- the attacker can reach the same route as the victim
Deployments using ordinary per-request authentication are not affected by this specific issue.
</details> ---Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐹Go | github.com/traefik/traefik/v3 | ≥ 3.0.0&&< 3.7.13 | 3.7.13go get github.com/traefik/traefik/v3@v3.7.13 |
| 🐹Go | github.com/traefik/traefik/v2 | ≥ 2.11.0&&< 2.11.57 | 2.11.57go get github.com/traefik/traefik/v2@v2.11.57 |
Affected Products
traefiktraefikDetection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for github.com/traefik/traefik/v3, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update github.com/traefik/traefik/v3 to 3.7.13 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-88007 is resolved across your whole dependency graph.
Workarounds
Put an independent control in front of the weakness: restrict the affected endpoint or interface to trusted networks, require an additional authentication factor or proxy-level check, and invalidate existing sessions and credentials in case the flaw has already been used.
Fixing This On Your OS
If you run this on a Linux distribution, patch through your package manager against the distro's own security advisory below — it tracks the exact backported fix for your release, which can ship on a different timeline (and sometimes a different severity) than the upstream project.
A flaw in Traefik's HTTP/3 ConnContext allows unauthenticated remote attackers to bypass authentication on backends using connection-bound NTLM or Negotiate protocols. Traefik fails to invoke service.AddTransportOnContext for HTTP/3 entrypoints, causing kerberosRoundTripper to share a single backend transport across…
Disable HTTP/3 on Traefik entrypoints handling traffic for backends utilizing NTLM or Negotiate authentication, or disable HTTP/3 globally in the static configuration by removing or setting 'experimental.http3' / entrypoint HTTP/3 settings to false until updated packages are deployed.Source: Red Hat security advisory for CVE-2026-88007 (CC BY 4.0)
Frequently Asked Questions
Is CVE-2026-88007 in your dependencies?
Find it across Go, including transitive dependencies.