CVE-2026-88009: Traefik HTTP Request Smuggling via Rootless Opaque Request-Target
Sending an HTTP/1.1 request with a rootless opaque target like 'GET http:http://internal-vhost/admin HTTP/1.1' tricks Traefik into routing and logging the request as '/' while forwarding the…

The problem
Traefik v3.0.0 through v3.7.12 (and v2 through v2.11.56) accept HTTP/1.x requests whose request-target is in rootless or opaque form, for example 'GET http:http://internal-vhost/admin HTTP/1.1'. Go's URL parser stores the target in URL.Opaque and leaves URL.Path empty, so every Traefik component that reads URL.Path or EscapedPath sees '/' instead of the real target.
The proxy's rewriteRequestBuilder sets Path, RawPath, and RawQuery on the outbound URL but never touches URL.Opaque. Because url.URL.RequestURI() returns Opaque in preference to the escaped path whenever Opaque is non-empty, the attacker's original bytes are written verbatim into the backend request line.
The result is three simultaneous consequences: cross-vhost routing bypass, path-scoped authorization bypass (forwardAuth policies see '/'), and complete access-log evasion (every such request is logged as 'GET / HTTP/1.1').
Proof of concept
A working proof-of-concept for CVE-2026-88009 in github.com/traefik/traefik/v3, with the exact payload below.
GET http:http://internal-vhost/admin HTTP/1.1
Host: app.example.com
Connection: close
Go's url.ParseRequestURI returns early with Opaque set whenever a scheme is present and the remainder does not start with '/'. The entry-point pipeline never checks Opaque: denyFragment inspects RawPath (empty, passes), normalizePath returns early on empty RawPath, and sanitizePath's JoinPath call rewrites Path to '/' but copies Opaque unchanged.
The muxer's withRoutingPath derives the routing path from EscapedPath(), which ignores Opaque, so path/prefix matchers always evaluate against '/'. On the way out, both the standard and fast proxy paths copy the URL struct without zeroing Opaque, so url.URL.RequestURI() hands the attacker's authority-bearing string directly to the backend's request line.
The encodedCharacters hardening (allowEncodedSlash=false) is also bypassed: it scans EscapedPath(), which is '/', so an opaque target carrying %2F bytes reaches the backend unchecked. The advisory's end-to-end reproduction on traefik:v3.7 confirmed 400 for a canonical /admin%2f..%2fsecret but 200 for the same payload in opaque form.
CWE-444 (HTTP Request Smuggling) and CWE-1286 (Improper Validation of Syntactic Correctness) both apply.
The fix
Upgrade to Traefik v3.7.13 (or v2.11.57 for the v2 line). PR #13796 (commit 58d1e9ca) adds a denyOpaque handler early in the entry-point chain that returns 400 Bad Request for any request where URL.Opaque is non-empty, and also zeroes URL.Opaque in both proxy code paths (pkg/proxy/httputil/proxy.go and pkg/proxy/fast/proxy.go) as a second barrier.
Traefik v3.0 through v3.6 are end-of-life and will not receive a backport; users on those branches must upgrade to v3.7.13.
Reported by sdelicata (Traefik maintainer, patch author).
Related research
- highCVE-2026-88008CVE-2026-88008: Traefik h2c Upgrade Middleware Bypass via HTTP Request Smuggling
- criticalCVE-2026-88007CVE-2026-88007: Traefik HTTP/3 NTLM Backend Connection Reuse
- highCVE-2026-88004CVE-2026-88004: Traefik Entrypoint Header Sanitization Bypass via HTTP Trailers
- high · 8.2CVE-2026-71327CVE-2026-71327: Traefik Gateway API Route Identity Collision Allows Cross-Namespace Backend Hijacking