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

Traefik StripPrefixRegex slices encoded RawPath by decoded-path length, so `/api%2e/admin/secret` reaches ForwardAuth as `/./admin/secret`, bypassing auth and serving protected content.

- Payout: Research
- Severity: high
- Program: Traefik
- Found by: gouldnicholas
- Disclosed: 2026-04-24
- CVE: CVE-2026-40912
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-6jwx-7vp4-9847
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-6jwx-7vp4-9847
- Tags: area:cloud domain:infra domain:web impact:internal-access kind:research platform:ghsa sev:high tech:go technique:parser-differential vuln:access-control vuln:auth-bypass vuln:path-traversal

**Target:** Reverse proxy middleware chain (StripPrefixRegex + ForwardAuth/BasicAuth/DigestAuth) in front of a normalizing backend
**Root cause:** StripPrefixRegex matches the regex against the decoded req.URL.Path but uses len(prefix) to slice the percent-encoded req.URL.RawPath; byte lengths diverge when percent-encoding exists in the prefix region, producing a wrong RawPath.
**Steps:**
1. Deploy Traefik with StripPrefixRegex (regex `^/api`) chained with ForwardAuth protecting `/admin/*`, backend Express static.
2. Confirm baseline: `curl http://localhost:8080/api/admin/secret` -> 403.
3. Send `curl --path-as-is "http://localhost:8080/api%2e/admin/secret"` -> 200.
4. Observe ForwardAuth log: `X-Forwarded-Uri: '/./admin/secret'` -> ALLOWED, while `/admin/secret` -> DENIED.
5. Backend normalizes `/./admin/secret` to `/admin/secret` per RFC 3986 and serves protected content.
**How found:** Reporter noticed the middleware matched the regex on decoded Path but sliced RawPath using that decoded length — a length mismatch when percent-encoding is present — then tested `%20` (backend didn't normalize, 404) and refined to `%2e` since dot-segment normalization is standard in Express, Go ServeMux, Spring Boot.
**Exploitation:** Unauthenticated attacker appends a percent-encoded character (`%2e`) in the prefix portion of a protected URL; StripPrefixRegex strips the decoded prefix length from the encoded RawPath, leaving a dot-segment (`/./admin/secret`) that ForwardAuth's path rules don't match, so it approves; the backend normalizes the dot-segment and returns the protected resource.
**Insight:** When a proxy strips a prefix, compare how it derives the stripped path (decoded length vs encoded bytes) and whether the auth layer and backend normalize dot-segments differently — `%2e` in the prefix region is the cheapest desync probe.

## Proof of concept (copied exactly from the original report)

**curl requests proving auth bypass via %2e**

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

**Vulnerable RawPath slicing code line**

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

**Exploit request chain with %2e dot-segment**

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

