Skip to content

Commit be5bb7c

Browse files
fix: normalize trailing slashes before length check in check_resource_allowed (#2074)
1 parent 705497a commit be5bb7c

File tree

3 files changed

+5
-16
lines changed

3 files changed

+5
-16
lines changed

src/mcp/client/auth/oauth2.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -493,12 +493,6 @@ async def _validate_resource_match(self, prm: ProtectedResourceMetadata) -> None
493493
if not prm_resource:
494494
return # pragma: no cover
495495
default_resource = resource_url_from_server_url(self.context.server_url)
496-
# Normalize: Pydantic AnyHttpUrl adds trailing slash to root URLs
497-
# (e.g. "https://example.com/") while resource_url_from_server_url may not.
498-
if not default_resource.endswith("/"):
499-
default_resource += "/"
500-
if not prm_resource.endswith("/"):
501-
prm_resource += "/"
502496
if not check_resource_allowed(requested_resource=default_resource, configured_resource=prm_resource):
503497
raise OAuthFlowError(f"Protected resource {prm_resource} does not match expected {default_resource}")
504498

src/mcp/shared/auth_utils.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,22 +51,17 @@ def check_resource_allowed(requested_resource: str, configured_resource: str) ->
5151
if requested.scheme.lower() != configured.scheme.lower() or requested.netloc.lower() != configured.netloc.lower():
5252
return False
5353

54-
# Handle cases like requested=/foo and configured=/foo/
54+
# Normalize trailing slashes before comparison so that
55+
# "/foo" and "/foo/" are treated as equivalent.
5556
requested_path = requested.path
5657
configured_path = configured.path
57-
58-
# If requested path is shorter, it cannot be a child
59-
if len(requested_path) < len(configured_path):
60-
return False
61-
62-
# Check if the requested path starts with the configured path
63-
# Ensure both paths end with / for proper comparison
64-
# This ensures that paths like "/api123" don't incorrectly match "/api"
6558
if not requested_path.endswith("/"):
6659
requested_path += "/"
6760
if not configured_path.endswith("/"):
6861
configured_path += "/"
6962

63+
# Check hierarchical match: requested must start with configured path.
64+
# The trailing-slash normalization ensures "/api123/" won't match "/api/".
7065
return requested_path.startswith(configured_path)
7166

7267

tests/shared/test_auth_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def test_check_resource_allowed_trailing_slash_handling():
104104
"""Trailing slashes should be handled correctly."""
105105
# With and without trailing slashes
106106
assert check_resource_allowed("https://example.com/api/", "https://example.com/api") is True
107-
assert check_resource_allowed("https://example.com/api", "https://example.com/api/") is False
107+
assert check_resource_allowed("https://example.com/api", "https://example.com/api/") is True
108108
assert check_resource_allowed("https://example.com/api/v1", "https://example.com/api") is True
109109
assert check_resource_allowed("https://example.com/api/v1", "https://example.com/api/") is True
110110

0 commit comments

Comments
 (0)