From 541822f07ff28b196bde09d3818fea0579becd86 Mon Sep 17 00:00:00 2001 From: Roee Hershko <113914991+roee-hersh@users.noreply.github.com> Date: Tue, 14 Oct 2025 22:00:28 +0300 Subject: [PATCH 1/3] add azp claim (#1945) Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> --- src/fastmcp/server/auth/providers/jwt.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/fastmcp/server/auth/providers/jwt.py b/src/fastmcp/server/auth/providers/jwt.py index c33d122ef..552654ff7 100644 --- a/src/fastmcp/server/auth/providers/jwt.py +++ b/src/fastmcp/server/auth/providers/jwt.py @@ -382,7 +382,12 @@ class JWTVerifier(TokenVerifier): claims = self.jwt.decode(token, verification_key) # Extract client ID early for logging - client_id = claims.get("client_id") or claims.get("sub") or "unknown" + client_id = ( + claims.get("client_id") + or claims.get("azp") + or claims.get("sub") + or "unknown" + ) # Validate expiration exp = claims.get("exp") From 2a20f54617a37213ed83894a8c2f0ac38a2e83a3 Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Tue, 14 Oct 2025 15:30:21 -0400 Subject: [PATCH 2/3] Escape all HTML to prevent XSS attack (#2090) --- src/fastmcp/client/oauth_callback.py | 4 +- src/fastmcp/utilities/ui.py | 9 +- tests/client/test_oauth_callback_xss.py | 159 ++++++++++++++++++++++++ 3 files changed, 167 insertions(+), 5 deletions(-) create mode 100644 tests/client/test_oauth_callback_xss.py diff --git a/src/fastmcp/client/oauth_callback.py b/src/fastmcp/client/oauth_callback.py index cf1166a80..ced483ea2 100644 --- a/src/fastmcp/client/oauth_callback.py +++ b/src/fastmcp/client/oauth_callback.py @@ -46,9 +46,7 @@ def create_callback_html( # Add detail info box for both success and error cases detail_info = "" if is_success and server_url: - detail_info = create_info_box( - f"Connected to: {server_url}", centered=True - ) + detail_info = create_info_box(f"Connected to: {server_url}", centered=True) elif not is_success: detail_info = create_info_box(message, is_error=True, centered=True) diff --git a/src/fastmcp/utilities/ui.py b/src/fastmcp/utilities/ui.py index 0d5c3bafd..e5a8429a2 100644 --- a/src/fastmcp/utilities/ui.py +++ b/src/fastmcp/utilities/ui.py @@ -7,6 +7,8 @@ consent pages, and other user-facing interfaces. from __future__ import annotations +import html + from starlette.responses import HTMLResponse # FastMCP branding @@ -339,6 +341,7 @@ def create_page( Returns: Complete HTML page as string """ + title = html.escape(title) return f""" @@ -375,6 +378,7 @@ def create_status_message(message: str, is_success: bool = True) -> str: Returns: HTML for status message """ + message = html.escape(message) icon = "✓" if is_success else "✕" icon_class = "success" if is_success else "error" @@ -400,6 +404,7 @@ def create_info_box( Returns: HTML for info box """ + content = html.escape(content) classes = ["info-box"] if is_error: classes.append("error") @@ -422,8 +427,8 @@ def create_detail_box(rows: list[tuple[str, str]]) -> str: rows_html = "\n".join( f"""