From 29aacce949a5575302371bdd86847daf6f20fde7 Mon Sep 17 00:00:00 2001 From: Sebastion Date: Thu, 26 Mar 2026 05:36:10 +0000 Subject: [PATCH] fix: reject URL-encoded path separators in resource template parameters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ResourceTemplate.matches() URL-decodes extracted parameters but did not re-validate that the decoded values still satisfy the [^/]+ segment constraint. An attacker could send a URI like: files://..%2F..%2Fetc%2Fpasswd The encoded %2F passes the regex match on the raw URI, but after unquote() it becomes ../../etc/passwd — a path traversal payload that is then passed directly to the template function via fn(**params). The fix re-checks every decoded parameter value against the original [^/]+ pattern and returns None (no match) if any value now contains a forward slash. --- .../server/mcpserver/resources/templates.py | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/mcp/server/mcpserver/resources/templates.py b/src/mcp/server/mcpserver/resources/templates.py index 2d612657c..f2a7f777c 100644 --- a/src/mcp/server/mcpserver/resources/templates.py +++ b/src/mcp/server/mcpserver/resources/templates.py @@ -19,6 +19,11 @@ from mcp.server.context import LifespanContextT, RequestT from mcp.server.mcpserver.context import Context +# Regex used for each URI template parameter segment. +# Decoded values must still satisfy this constraint to prevent +# encoded path-separator injection (e.g. ``%2F`` → ``/``). +_SEGMENT_RE = re.compile(r"[^/]+") + class ResourceTemplate(BaseModel): """A template for dynamically creating resources.""" @@ -86,13 +91,28 @@ def matches(self, uri: str) -> dict[str, Any] | None: """Check if URI matches template and extract parameters. Extracted parameters are URL-decoded to handle percent-encoded characters. + After decoding, each value is re-validated to ensure it does not contain + a ``/`` character, which would indicate an encoded path separator bypass + (e.g. ``%2F``). Rejecting such values prevents path-traversal attacks + where an attacker could send ``..%2F..%2Fetc%2Fpasswd`` to escape the + intended path segment. """ # Convert template to regex pattern pattern = self.uri_template.replace("{", "(?P<").replace("}", ">[^/]+)") match = re.match(f"^{pattern}$", uri) if match: # URL-decode all extracted parameter values - return {key: unquote(value) for key, value in match.groupdict().items()} + decoded = {key: unquote(value) for key, value in match.groupdict().items()} + + # Reject any decoded value that would not have matched the + # original ``[^/]+`` segment constraint. This blocks encoded + # slash injection (``%2F`` → ``/``) which could allow path + # traversal when the parameter is used in file-system operations. + for value in decoded.values(): + if not _SEGMENT_RE.fullmatch(value): + return None + + return decoded return None async def create_resource(