From 5be4ed911e135a1ef050b820e394fda9d185d341 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Fri, 24 Apr 2026 18:23:59 -0700 Subject: [PATCH] fix: preserve trailing slash when joining pass-through paths posixpath.normpath strips trailing slashes, which silently changed endpoints like /v1/models/ to /v1/models. Track whether the original input ended in a slash and re-append it after normalization. --- .../pass_through_endpoints.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py index 70fdd3ceb88..cc541182b23 100644 --- a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py @@ -601,10 +601,15 @@ class HttpPassThroughEndpointHelpers(BasePassthroughUtils): subpath = subpath[1:] # Resolve any '..' segments in the subpath so it cannot climb above - # the base_target prefix that the operator configured. + # the base_target prefix that the operator configured. Preserve a + # trailing slash on the original subpath since some upstreams treat + # `/foo` and `/foo/` as different resources. + trailing_slash = subpath.endswith("/") safe_subpath = posixpath.normpath("/" + subpath).lstrip("/") if safe_subpath == ".": safe_subpath = "" + if trailing_slash and safe_subpath and not safe_subpath.endswith("/"): + safe_subpath += "/" return base_target + safe_subpath @@ -615,11 +620,14 @@ class HttpPassThroughEndpointHelpers(BasePassthroughUtils): Preserves any path prefix configured on the base URL and resolves ``..`` segments in the endpoint so the result stays within the base - path. + path. A trailing slash on ``endpoint_path`` is preserved. """ + trailing_slash = endpoint_path.endswith("/") base_path = base_url.path or "" if not base_path or base_path == "/": normalized_endpoint = posixpath.normpath("/" + endpoint_path.lstrip("/")) + if trailing_slash and normalized_endpoint != "/": + normalized_endpoint += "/" return normalized_endpoint base_path = base_path.rstrip("/") @@ -628,6 +636,8 @@ class HttpPassThroughEndpointHelpers(BasePassthroughUtils): # If normalization climbs out of the base path, fall back to base. if combined != base_path and not combined.startswith(base_path + "/"): return base_path + "/" + if trailing_slash and not combined.endswith("/"): + combined += "/" return combined @staticmethod