Load Balancer Logic Flaws: Shadow-Path [Deep Dive]
Bottom Line
If the proxy and the backend interpret the same URL differently, your access control can fail without any broken crypto or memory corruption. CVE-2025-66490 is a clean example of why path parsing is part of the security boundary.
Key Takeaways
- ›CVE-2025-66490 affects Traefik path-based routing in versions before 2.11.32 and 3.6.3.
- ›The flaw is a split-view bug: Traefik matches one path view while some backends act on another.
- ›Encoded reserved characters like %2F can bypass middleware when routers overlap on the same entrypoint.
- ›Hardening now depends on explicit encoded-character policy plus keeping sanitizePath enabled.
The most interesting edge security bugs are often logic flaws, not memory corruption. CVE-2025-66490 in Traefik exposed exactly that: a request path could be interpreted one way by the load balancer and another way by the backend, allowing middleware on protected routes to be skipped. There is no official vendor nickname for this issue, but “Shadow-Path” is a useful shorthand for the bug class: a second, more dangerous path interpretation hiding behind the first.
- Patched versions: v2.11.32 and v3.6.3 in the GitHub advisory.
- Trigger: path matchers such as PathPrefix, Path, or PathRegex combined with encoded reserved characters.
- Impact: middleware and routing policy can be bypassed, sending requests to unintended backends.
- Operator lesson: upgrades alone are not enough if your ingress policy still assumes every component parses URLs identically.
CVE Summary Card
Bottom Line
This was not a classic parser crash. It was a trust-boundary failure: the router, middleware chain, and backend could disagree about what resource the request actually targeted.
- CVE: CVE-2025-66490
- Product: Traefik HTTP reverse proxy and load balancer
- Published: December 8, 2025 in the GitHub advisory and NVD record
- Affected versions: vendor advisory says <= v2.11.31 and <= v3.6.2; NVD lists fixes at 2.11.32 and 3.6.3
- Severity: High in the GitHub advisory; CVSS v3.1 6.5 and CVSS v4.0 6.9 in NVD
- CWE: CWE-436, interpretation conflict
- Exploit condition: path-based routing plus another router on the same entrypoint plus a backend that decodes reserved characters
Why “Shadow-Path” fits
The public advisory calls this a path normalization bypass. That is accurate, but incomplete for architects. The deeper issue is the existence of two path realities: the one used to decide which middleware runs, and the one the application eventually processes. Security policy was attached to the first path view, while risk lived in the second.
Vulnerable Code Anatomy
The split-view bug class
Traefik had already introduced important path handling changes before the CVE. In v2.11.24 it added sanitizePath, and in v2.11.25 it normalized unreserved characters while intentionally keeping reserved characters encoded during router matching. That behavior tracks RFC 3986 more closely, but it does not guarantee that the backend will make the same choice.
The vulnerable pattern appears when all of the following are true:
- A protected router matches something like
/admin/and applies auth, block, or rewrite middleware. - A broader fallback router matches
/on the same host and entrypoint. - The incoming request contains an encoded reserved character such as
%2F. - The backend framework or app stack decodes that reserved character before resource handling.
// Conceptual only: illustrates the logic mismatch, not runnable exploit code.
incomingPath = "/admin%2F"
routerPath = keepReservedCharsEncoded(incomingPath)
if match(PathPrefix("/admin/"), routerPath) {
apply("security-middleware")
forward(serviceA)
} else if match(PathPrefix("/"), routerPath) {
forward(serviceA)
}
backendPath = backendDecodesReservedChars(incomingPath)
// backendPath becomes "/admin/" and reaches sensitive logic
Why normalization alone was not enough
- Normalization is local. It only helps if every hop uses the same rules and the same timing.
- Reserved characters are semantic. Decoding
%2Fcan change path structure, not just formatting. - Middleware is route-scoped. If the protected router never matches, the security controls attached to it never execute.
- Fallback routes hide danger. A broad
PathPrefix('/')makes the bypass operational instead of theoretical.
This is why the Traefik fix focused on rejecting suspicious encoded characters at the edge rather than trying to outguess every possible backend interpretation.
Attack Timeline
- November 2025 groundwork: Traefik’s v2.11.24 and v2.11.25 changes tightened sanitization and normalization, including the decision to keep reserved characters encoded during route matching.
- December 4, 2025: PR #12360, titled Reject suspicious encoded characters, was merged into the v2.11 branch. The PR explicitly says it was meant to block “split view scenarios between Traefik and the backends.”
- December 4, 2025: Traefik published v2.11.32 release notes listing CVE-2025-66490 and the fix [server] Reject suspicious encoded characters.
- December 5, 2025: Traefik’s v3.6.4 public release notes carried the same fix and migration warning, while the advisory identifies v3.6.3 as the patched 3.x baseline.
- December 8, 2025: GitHub published advisory GHSA-gm3x-23wp-hc2c, and NVD recorded the CVE the same day.
- March 6, 2026: NVD reanalyzed the record and clarified affected CPE ranges for both the 2.11 and 3.x lines.
- Later in the 2.11 line: Traefik documentation for v2.11.35 shows the encoded-character options defaulting back to true, shifting responsibility from secure-by-default blocking to explicit operator hardening.
That last point matters. The vendor response evolved from immediate safety toward compatibility, which is reasonable operationally but increases the burden on platform teams to understand their own backend parsing behavior.
Exploitation Walkthrough
Common preconditions
- The deployment uses path-based policy to protect a high-value route such as
/admin/,/internal/, or/beta/. - A more permissive router still accepts requests for the same host or entrypoint.
- The application stack or framework decodes reserved characters before dispatch.
- The security team assumes the load balancer and backend share one canonical path model.
How the bypass unfolds
- An attacker identifies a protected path that is guarded by middleware rather than by application-level authorization alone.
- The attacker sends a request whose path preserves the protected resource semantically but hides structure with an encoded reserved character, typically a slash-equivalent path transition.
- Traefik evaluates router rules against its own path representation, where the protected rule does not match as intended.
- The broader router accepts the request and forwards it without the expected middleware chain.
- The backend decodes the reserved character, reconstructs the sensitive path shape, and serves logic that should have remained behind the protected route.
The critical mistake is assuming that access control at the proxy remains valid after the application rewrites or reinterprets the target resource. Once those semantics diverge, the edge policy becomes advisory rather than authoritative.
What the real blast radius looks like
- Admin endpoints can become reachable through the wrong router.
- Forward-auth chains can be skipped when attached only to the protected path rule.
- Observability gets messy because logs may show the encoded path at the edge and the decoded path inside the app.
- Incident response slows down because teams argue about whether the proxy or the app “really” handled the request.
Hardening Guide
The immediate move is to upgrade, but the durable fix is to make path interpretation explicit at every trust boundary. In current Traefik documentation, the encoded-character controls exist per entrypoint and should be treated like security policy, not compatibility toggles.
entryPoints:
websecure:
address: ':443'
http:
sanitizePath: true
encodedCharacters:
allowEncodedSlash: false
allowEncodedBackSlash: false
allowEncodedNullCharacter: false
allowEncodedSemicolon: false
allowEncodedPercent: false
allowEncodedQuestionMark: false
allowEncodedHash: false
If you prefer CLI-based static configuration, the documented pattern is the same, for example --entryPoints.web.http.encodedCharacters.allowEncodedSlash=false.
Practical hardening checklist
- Upgrade first. Move off versions prior to 2.11.32 and 3.6.3.
- Keep
sanitizePathenabled. Traefik’s docs explicitly warn that setting it to false is not safe. - Deny risky encoded characters explicitly. Do not rely on defaults, especially after the later default-value changes in the 2.11 line.
- Collapse overlapping routers. Avoid pairing high-priority protected prefixes with a broad catch-all route on the same surface unless app auth duplicates the protection.
- Mirror auth in the application. Proxy middleware should narrow exposure, not be the only control protecting sensitive business logic.
- Test backend parsing behavior. Frameworks, language runtimes, and upstream app servers do not all treat encoded reserved characters the same way.
- Diff logs across layers. Compare edge logs, service mesh logs, and app logs for path representation drift.
When you need to share suspicious request samples with teammates or vendors, sanitize them first. A utility like the Data Masking Tool is useful for redacting tokens, user identifiers, and internal path fragments before the examples leave your incident channel.
Architectural Lessons
Security boundaries must share one path model
- Canonicalization is part of authorization. If different layers canonicalize differently, your policy graph is already inconsistent.
- Standards compliance is not enough by itself. A proxy can be RFC-aligned and still unsafe when paired with a backend that decodes more aggressively.
- Compatibility defaults are risky. Vendor defaults often move toward least-breakage, not strongest isolation.
- Path-based controls are brittle. They work best when combined with identity-aware checks inside the service.
What teams should change in design reviews
- Ask which layer owns the canonical resource identity for an HTTP request.
- Document whether the backend decodes reserved characters before routing.
- Require negative tests for encoded path variants whenever a route protects admin, debug, tenant-isolation, or internal APIs.
- Prefer application authorization tied to resource semantics over proxy-only middleware tied to string patterns.
The larger lesson from CVE-2025-66490 is that modern load balancers are not just transport components. They are parsers, policy engines, and partial security gateways. Once they take on that role, every mismatch between proxy semantics and backend semantics becomes a potential vulnerability surface. Shadow paths are what appear when those semantics drift apart.
Frequently Asked Questions
What is CVE-2025-66490 in Traefik? +
Am I affected if my backend never decodes %2F or other reserved characters? +
Is Traefik sanitizePath enough to stop this class of bug? +
sanitizePath helps with path cleanup such as dot segments and duplicate slashes, and Traefik explicitly warns against disabling it, but it does not solve every reserved-character interpretation mismatch. You also need an explicit encoded-character policy and application-side authorization for sensitive routes.What should I configure in Traefik after upgrading? +
sanitizePath enabled and set the documented entryPoints.<name>.http.encodedCharacters.* options deliberately for your threat model. If your backend decodes reserved characters, set the relevant allowEncoded* controls to false so the edge rejects ambiguous requests before they hit the app.Get Engineering Deep-Dives in Your Inbox
Weekly breakdowns of architecture, security, and developer tooling — no fluff.