From c9e7fd829c0f6f562d131c0a856f32d90c6df4e8 Mon Sep 17 00:00:00 2001 From: Yassin Kortam Date: Wed, 10 Jun 2026 20:25:37 -0700 Subject: [PATCH] fix(auth_v2): prefer verified mTLS cert and confirm forwarded-DN gates on the socket peer The forwarded subject-DN trust gate already compared the raw transport peer (request.client.host / ASGI scope client) against trusted_proxy_cidrs, never the XFF-resolved IP, so an attacker spoofing X-Forwarded-For cannot defeat it. Make that ordering explicit and stronger: a genuinely verified client certificate from the ASGI TLS extension is now preferred when present, and the spoofable forwarded-header path is only consulted as a fallback, still gated on the direct socket peer. --- litellm/proxy/auth_v2/authenticators.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/auth_v2/authenticators.py b/litellm/proxy/auth_v2/authenticators.py index 44cba56332b..cf5715decb7 100644 --- a/litellm/proxy/auth_v2/authenticators.py +++ b/litellm/proxy/auth_v2/authenticators.py @@ -404,15 +404,17 @@ class MutualTLSAuthenticator: ) def _read_client_cert(self, request: Request) -> Optional[ClientCertificate]: + tls = request.scope.get("extensions", {}).get("tls", {}) + verified_dn = tls.get("client_cert_name") + if verified_dn: + return ClientCertificate(subject_dn=verified_dn) if self._config.forwarded_subject_header: peer = request.client.host if request.client else None if not ip_in_trusted_proxies(peer, self._network): return None dn = request.headers.get(self._config.forwarded_subject_header) return ClientCertificate(subject_dn=dn) if dn else None - tls = request.scope.get("extensions", {}).get("tls", {}) - dn = tls.get("client_cert_name") - return ClientCertificate(subject_dn=dn) if dn else None + return None def challenge(self) -> str: return ""