Skip to content

Commit 9595740

Browse files
committed
fix: do not percent-decode query parameter names in match
RFC 6570 expansion never percent-encodes variable names, so a legitimate match will always have the parameter name in literal form. Decoding names before the duplicate-key check let an attacker shadow a real parameter by prepending a percent-encoded duplicate: api://x?%74oken=evil&token=real -> {token: evil} With this change the encoded form is treated as an unrecognized parameter and ignored, so the literal form wins.
1 parent 7629f62 commit 9595740

File tree

2 files changed

+11
-1
lines changed

2 files changed

+11
-1
lines changed

src/mcp/shared/uri_template.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -560,13 +560,18 @@ def _parse_query(query: str) -> dict[str, str]:
560560
``+`` as space for HTML form submissions, but RFC 6570 and MCP
561561
resource URIs follow RFC 3986 where only ``%20`` encodes a space.
562562
563+
Parameter names are **not** percent-decoded. RFC 6570 expansion
564+
never encodes variable names, so a legitimate match will always
565+
have the name in literal form. Decoding names would let
566+
``%74oken=evil&token=real`` shadow the real ``token`` parameter
567+
via first-wins.
568+
563569
Duplicate keys keep the first value. Pairs without ``=`` are
564570
treated as empty-valued.
565571
"""
566572
result: dict[str, str] = {}
567573
for pair in query.split("&"):
568574
name, _, value = pair.partition("=")
569-
name = unquote(name)
570575
if name and name not in result:
571576
result[name] = unquote(value)
572577
return result

tests/shared/test_uri_template.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,11 @@ def test_expand_rejects_invalid_value_types(value: object):
450450
("search{?q}", "search?&q=hello&", {"q": "hello"}),
451451
# Duplicate query keys keep first value
452452
("search{?q}", "search?q=first&q=second", {"q": "first"}),
453+
# Percent-encoded parameter names are NOT decoded: RFC 6570
454+
# expansion never encodes names, so an encoded name cannot be
455+
# a legitimate match. Prevents HTTP parameter pollution.
456+
("api://x{?token}", "api://x?%74oken=evil&token=real", {"token": "real"}),
457+
("api://x{?token}", "api://x?%74oken=evil", {}),
453458
# Level 3: query continuation with literal ? falls back to
454459
# strict regex (template-order, all-present required)
455460
("?a=1{&b}", "?a=1&b=2", {"b": "2"}),

0 commit comments

Comments
 (0)