diff --git a/client/src/components/Sidebar.tsx b/client/src/components/Sidebar.tsx index 13a4f24f..762678f2 100644 --- a/client/src/components/Sidebar.tsx +++ b/client/src/components/Sidebar.tsx @@ -15,6 +15,7 @@ import { Copy, CheckCheck, Server, + AlertTriangle, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -42,6 +43,7 @@ import CustomHeaders from "./CustomHeaders"; import { CustomHeaders as CustomHeadersType } from "@/lib/types/customHeaders"; import { useToast } from "../lib/hooks/useToast"; import IconDisplay, { WithIcons } from "./IconDisplay"; +import { validateRedirectUrl } from "@/utils/urlValidation"; interface SidebarProps { connectionStatus: ConnectionStatus; @@ -782,44 +784,72 @@ const Sidebar = ({ - {connectionStatus === "connected" && serverImplementation && ( -
-
- {(serverImplementation as WithIcons).icons && - (serverImplementation as WithIcons).icons!.length > 0 ? ( - - ) : ( - - )} - {(serverImplementation as { websiteUrl?: string }) - .websiteUrl ? ( - - {serverImplementation.name || "MCP Server"} - - ) : ( - - {serverImplementation.name || "MCP Server"} - - )} -
- {serverImplementation.version && ( -
- Version: {serverImplementation.version} + {connectionStatus === "connected" && + serverImplementation && + (() => { + const websiteUrl = ( + serverImplementation as { websiteUrl?: string } + ).websiteUrl; + let isValidWebsiteUrl = false; + if (websiteUrl) { + try { + validateRedirectUrl(websiteUrl); + isValidWebsiteUrl = true; + } catch { + isValidWebsiteUrl = false; + } + } + const serverName = serverImplementation.name || "MCP Server"; + return ( +
+
+ {(serverImplementation as WithIcons).icons && + (serverImplementation as WithIcons).icons!.length > 0 ? ( + + ) : ( + + )} + {websiteUrl && isValidWebsiteUrl ? ( + + {serverName} + + ) : ( + + {serverName} + + )} +
+ {websiteUrl && !isValidWebsiteUrl && ( + + +
+ + + Potentially malicious websiteURL field + +
+
+ +

{websiteUrl}

+
+
+ )} + {serverImplementation.version && ( +
+ Version: {serverImplementation.version} +
+ )}
- )} -
- )} + ); + })()} {loggingSupported && connectionStatus === "connected" && (
diff --git a/client/src/components/__tests__/Sidebar.test.tsx b/client/src/components/__tests__/Sidebar.test.tsx index 03e898ca..460161e5 100644 --- a/client/src/components/__tests__/Sidebar.test.tsx +++ b/client/src/components/__tests__/Sidebar.test.tsx @@ -1046,4 +1046,68 @@ describe("Sidebar", () => { ); }); }); + + describe("Server Implementation websiteUrl", () => { + it("should render a warning instead of a link when websiteUrl uses an unsafe protocol", () => { + renderSidebar({ + connectionStatus: "connected", + serverImplementation: { + name: "Evil Server", + version: "1.0.0", + websiteUrl: "javascript:alert('xss')", + }, + }); + + // The warning message should be displayed + expect( + screen.getByText("Potentially malicious websiteURL field"), + ).toBeInTheDocument(); + + // The server name should still be shown + expect(screen.getByText("Evil Server")).toBeInTheDocument(); + + // No link should have been rendered for the malicious URL + expect( + screen.queryByRole("link", { name: "Evil Server" }), + ).not.toBeInTheDocument(); + }); + + it("should render a link when websiteUrl is a valid https URL", () => { + renderSidebar({ + connectionStatus: "connected", + serverImplementation: { + name: "Good Server", + version: "1.0.0", + websiteUrl: "https://example.com", + }, + }); + + const link = screen.getByRole("link", { name: "Good Server" }); + expect(link).toBeInTheDocument(); + expect(link).toHaveAttribute("href", "https://example.com"); + + expect( + screen.queryByText("Potentially malicious websiteURL field"), + ).not.toBeInTheDocument(); + }); + + it("should render a link when websiteUrl is a valid http URL", () => { + renderSidebar({ + connectionStatus: "connected", + serverImplementation: { + name: "Local Server", + version: "1.0.0", + websiteUrl: "http://localhost:3000", + }, + }); + + const link = screen.getByRole("link", { name: "Local Server" }); + expect(link).toBeInTheDocument(); + expect(link).toHaveAttribute("href", "http://localhost:3000"); + + expect( + screen.queryByText("Potentially malicious websiteURL field"), + ).not.toBeInTheDocument(); + }); + }); });