Introduction
Traefik deployments that put ForwardAuth in front of Windows Integrated Authentication could hand one user's authenticated identity to a completely unrelated, unauthenticated client. Because backends trust the identity headers Traefik copies from the auth server's response, that second client is processed as the first user, with whatever permissions that user holds.
Traefik Proxy is the reverse proxy at the center of Traefik Labs' portfolio, which also includes API gateway and API management products and solutions for Kubernetes ingress and application connectivity. An OWASP announcement from March 19, 2024 described Traefik Proxy as one of Docker Hub's top ten projects with over 3 billion downloads. That figure reflects historical ecosystem reach rather than a count of vulnerable installations, but it explains why an isolation bug in its authentication middleware is worth understanding precisely.
On severity, the CVE record lists a score of 8.7, while the NVD entry displays VulnCheck's CVSS v4.0 assessment of 7.0 High, with no separate NIST assessment. As of November 4, 2026, public reporting does not identify exploited organizations, a campaign, or a named threat actor, so exploitation is best described as unconfirmed. The vendor credits researcher mosafariuk with the report.
Technical Information
ForwardAuth and authResponseHeaders
ForwardAuth delegates authentication to an external service. If the auth service answers with a 2XX response, Traefik lets the original request proceed. Otherwise, Traefik returns the auth service's response to the client. The authResponseHeaders option copies selected headers from the auth response into the request forwarded to the backend, replacing any conflicting headers. In practice this is how an identity such as a username reaches the protected application.
Root cause: connection-bound authentication over a shared pool
NTLM and Kerberos/SPNEGO "Negotiate" authenticate a TCP connection, not a single request. As the author of the fix puts it: "NTLM and Kerberos authenticate the connection, not the request." IIS Windows Authentication is the canonical example the vendor cites.
Before the patch, ForwardAuth sent its subrequests through an http.Client whose transport was http.DefaultTransport, or a TLS clone of it. That connection pool was shared by every client of the middleware. Two details combined into the bug:
- Shared pool. A connection to the auth server that had been authenticated by one user's NTLM or Negotiate exchange went back into the same pool used for every other client.
- Lost context. The subrequest was built with
http.NewRequest, which drops the incoming request's context. Traefik already had a mechanism (the "Kerberos round tripper") that keeps a separate transport for each frontend connection when proxying to backends, but it locates the right transport through the request context. Without that context, isolation could never apply to ForwardAuth.
The vulnerable subrequest construction:
forwardReq, err := http.NewRequest(http.MethodGet, fa.address, nil)
The vulnerable client setup, where the TLS case assigned a single shared transport:
fa.client.Transport = tr
The weakness maps to CWE-488, exposure of a data element to the wrong session. Here the "data element" is the authenticated state of an auth server socket.
Attack flow
The following sequence is based on the vendor advisory and is illustrative, not a reported incident:
- A legitimate user (call her alice) reaches a route protected by ForwardAuth backed by IIS Windows Authentication. Traefik's auth subrequest carries her NTLM or Negotiate credential and the auth server marks that TCP connection as authenticated for alice.
- The auth server responds with a 2XX and identity headers, and alice's request proceeds. The now-authenticated auth server socket returns to Traefik's shared pool.
- An unauthenticated client (bob) connects on a separate frontend connection and sends a request with no credential.
- Traefik issues a ForwardAuth subrequest for bob. The shared pool hands out the socket that is still authenticated as alice.
- The auth server treats the request as coming from alice and returns a successful response containing alice's identity.
- Traefik copies the configured
authResponseHeadersinto bob's forwarded request. The backend processes bob as alice.
The attacker does not need alice's credentials and does not forge any header. The identity comes from the reuse of authenticated connection state. The resulting access is bounded by the inherited user's permissions and by backend authorization. Public reporting cautions that the advisory does not establish unrestricted administrative control over every affected application.
Reading the vector
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:N/SC:H/SI:H/SA:N
The vector describes a network attack with low complexity, no privileges, and no user interaction, but with attack requirements present (AT:P): the auth service must use connection-bound authentication and an authenticated connection must be available for reuse. Impact lands on subsequent systems (high confidentiality and integrity on the backends), with no direct impact on Traefik itself and no availability impact.
Ingress NGINX auth-url in Traefik 3.7
In Traefik 3.7, the same isolation failure also affects external authentication requests generated by the Kubernetes Ingress NGINX provider's auth-url annotation. Deployments migrating Ingress NGINX annotations to Traefik should treat that path the same way as ForwardAuth.
Patch Information
Traefik fixed this in v2.11.60 (PR #14084) and v3.7.16 (PR #14091 plus #14084). Both releases came out on October 20, 2026, and the release notes for each list GHSA-hrpp-p4fc-wwf8 as fixed. The advisory states that v3.0 through v3.6 are end of life and receive no fix of their own, so users on those branches have to move to a patched line. No configuration workaround is documented.
One auth server connection per client connection
The patch reuses the existing Kerberos round tripper for ForwardAuth. In v2.11, pkg/middlewares/auth/forward.go no longer gives the client a single shared transport. Instead, it wraps both a shared transport and a factory that builds a fresh clone for each connection:
transport := http.DefaultTransport newTransport := func() http.RoundTripper { return http.DefaultTransport.(*http.Transport).Clone() } ... transport = tr newTransport = func() http.RoundTripper { return tr.Clone() } } fa.client.Transport = service.NewKerberosRoundTripper(transport, newTransport)
When config.TLS is set, each per-connection clone is copied from the TLS-configured transport, so auth server TLS settings carry over. A small constructor, NewKerberosRoundTripper, was added to pkg/server/service/roundtripper.go so the middleware can create the wrapper.
Context is the key change
The round tripper finds the right transport through the request context, which the entrypoint populates for each frontend connection using AddTransportOnContext. The patched subrequest keeps that context:
// The request context carries the transports dedicated to the client connection (see service.AddTransportOnContext). forwardReq, err := http.NewRequestWithContext(req.Context(), http.MethodGet, fa.address, nil)
How the round tripper decides
PR #14091 moves the logic into its own package, pkg/server/service/kerberos, to avoid an import cycle. The routing rule:
if rt := connRoundTrippers.get(k); rt != nil { return rt.RoundTrip(request) } ... if containsNTLMorNegotiate(request.Header.Values("Authorization")) { return connRoundTrippers.stick(k).RoundTrip(request) } return k.original.RoundTrip(request)
In plain terms:
- If the frontend connection already has its own transport, every later request on it uses that transport. This covers HTTP/2 streams and later requests that carry no credential.
- If the request has an
Authorizationheader with theNTLMorNegotiatescheme, a dedicated transport is created and pinned ("stuck") to the connection. Scheme matching is case insensitive per RFC 9110, sontlmandnegotiatematch butNTLMishdoes not. - Anything else goes through the shared pool, so ordinary traffic keeps the benefit of connection reuse.
An authenticated socket therefore never returns to the shared pool where another client could pick it up. stick() takes a mutex and keeps whatever transport is already stored, so concurrent HTTP/2 streams on one connection cannot race and orphan backend connections.
Ingress NGINX auth-url (v3.7 only)
PR #14091 applies the same change to the auth client in pkg/middlewares/ingressnginx/snippet/snippet.go, which serves the auth-url annotation:
Transport: kerberos.NewRoundTripper(http.DefaultTransport, func() http.RoundTripper { return http.DefaultTransport.(*http.Transport).Clone() }),
The HTTP/1.1, HTTP/2 and HTTP/3 entrypoints (server_entrypoint_tcp.go, server_entrypoint_tcp_http3.go) now call kerberos.AddTransportOnContext, and the backend TransportManager uses the same kerberos.NewRoundTripper. ForwardAuth, auth-url and backend proxying now share one isolation mechanism.
Regression tests
Both PRs add TestForwardAuthConnectionBoundAuthentication, run against plain HTTP and HTTPS auth servers. The mock auth server behaves like IIS Windows Authentication: an NTLM header authenticates the TCP connection, and later requests on that connection without credentials are treated as the same user. The test asserts the attack sequence:
- alice authenticates and gets
200withX-Auth-User: alice. - bob, on his own client connection and with no credential, must get
401and an empty user. - alice's next request without a credential still succeeds as alice.
The new kerberos package also has unit tests for challenge-first Negotiate flows, lowercase schemes, lookalike schemes, and pinning only once under concurrent load.
Follow-up release: choose 2.11.61 or 3.7.17
Pinning a transport to each connection has a resource cost. Traefik later published GHSA-xv5x-242w-7hf7, fixed in 2.11.61 and 3.7.17. The announcement says only 2.11.58 through 2.11.60 and 3.7.14 through 3.7.16 are affected, and 3.6 and earlier are not. In those versions, an unauthenticated client could send an NTLM or Negotiate header to make Traefik keep a dedicated backend connection for every client connection, eventually exhausting process memory. Valid credentials and an NTLM backend are not required. The 2.11.60 and 3.7.16 releases close the identity leak but fall inside that range, so the builds that contain both fixes are 2.11.61 and 3.7.17. These are two distinct vulnerabilities, and the later one does not invalidate the original isolation fix.
Affected Systems and Versions
Vulnerable to CVE-2026-121966:
- Traefik 2.x before 2.11.60 (fixed in 2.11.60).
- Traefik 3.0 through 3.6: affected and end of life, with no fix on these branches. Migrate to a patched 3.7 release.
- Traefik 3.7 through 3.7.15 (fixed in 3.7.16).
Vulnerable configurations:
- ForwardAuth middleware pointing at an auth server that uses connection-bound authentication: NTLM or Kerberos/SPNEGO Negotiate, including IIS Windows Authentication.
- In Traefik 3.7 only, the Kubernetes Ingress NGINX provider's
auth-urlannotation against the same kind of auth server. - The identity reaches the backend through
authResponseHeaders.
ForwardAuth deployments whose auth service establishes authentication independently for each request are not described as affected. Exposure depends on a vulnerable release combined with connection-bound authentication, and having Traefik installed is not enough on its own.
Related but separate (GHSA-xv5x-242w-7hf7, resource exhaustion): 2.11.58 through 2.11.60 and 3.7.14 through 3.7.16, fixed in 2.11.61 and 3.7.17.
Vendor Security History
Traefik Labs' security page states that it reproduces findings before accepting them, keeps reports private until a fix is available, and publishes affected ranges, CVSS v4 vectors, available mitigations and reporter credit in public GitHub advisories. This case is consistent with that process. The fixed releases shipped on October 20, 2026, the advisory was published on October 21, 2026, and the public announcement explained the mechanism and stated plainly that the 3.0 through 3.6 branches would not be patched.
The record also shows a limitation. The follow-up advisory identified a resource exhaustion issue introduced alongside the transport pinning approach, in the same releases that carried the identity fix, and required another round of releases. The available material does not provide a broader breach history or comparative vulnerability data, so we do not draw conclusions beyond this sequence.
References
- NVD: CVE-2026-121966
- CVE.org record: CVE-2026-121966
- GitHub Security Advisory GHSA-hrpp-p4fc-wwf8
- GHSA-hrpp-p4fc-wwf8 (GitHub API)
- VulnCheck advisory
- PR #14084: Support NTLM and Kerberos authentication in ForwardAuth
- PR #14084 issue (GitHub API)
- PR #14084 changed files (GitHub API)
- PR #14091: Support NTLM and Kerberos in ForwardAuth and ingress nginx auth url
- PR #14091 issue (GitHub API)
- PR #14091 changed files (GitHub API)
- Release v2.11.60
- Release v2.11.60 (GitHub API)
- Release v3.7.16
- Release v3.7.16 (GitHub API)
- Traefik community: security update 2.11.60 and 3.7.16
- Traefik community: security update 2.11.61 and 3.7.17
- GitHub Security Advisory GHSA-xv5x-242w-7hf7 (follow-up resource exhaustion)
- Traefik ForwardAuth documentation
- Security at Traefik Labs
- About Traefik Labs
- OWASP: Traefik Labs joins OWASP
- Tenable plugin 371932: FreeBSD traefik ForwardAuth vulnerability
- Cyber Insider coverage



