CVE-2025-58444
Description
The MCP inspector is a developer tool for testing and debugging MCP servers. A cross-site scripting issue was reported in versions of the MCP Inspector local development tool prior to 0.16.6 when connecting to untrusted remote MCP servers with a malicious redirect URI. This could be leveraged to interact directly with the inspector proxy to trigger arbitrary command execution. Users are advised to update to 0.16.6 to resolve this issue.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
@modelcontextprotocol/inspectornpm | < 0.16.6 | 0.16.6 |
Affected products
1- Range: 0.0.1, 0.1.0, 0.10.2, …
Patches
2377211061b51Merge pull request #777 from cliffhall/bump-version-to-0.16.6
5 files changed · +15 −15
client/package.json+1 −1 modified@@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-client", - "version": "0.16.5", + "version": "0.16.6", "description": "Client-side application for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)",
cli/package.json+1 −1 modified@@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-cli", - "version": "0.16.5", + "version": "0.16.6", "description": "CLI for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)",
package.json+4 −4 modified@@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector", - "version": "0.16.5", + "version": "0.16.6", "description": "Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -47,9 +47,9 @@ "check-version": "node scripts/check-version-consistency.js" }, "dependencies": { - "@modelcontextprotocol/inspector-cli": "^0.16.5", - "@modelcontextprotocol/inspector-client": "^0.16.5", - "@modelcontextprotocol/inspector-server": "^0.16.5", + "@modelcontextprotocol/inspector-cli": "^0.16.6", + "@modelcontextprotocol/inspector-client": "^0.16.6", + "@modelcontextprotocol/inspector-server": "^0.16.6", "@modelcontextprotocol/sdk": "^1.17.5", "concurrently": "^9.2.0", "open": "^10.2.0",
package-lock.json+8 −8 modified@@ -1,22 +1,22 @@ { "name": "@modelcontextprotocol/inspector", - "version": "0.16.5", + "version": "0.16.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@modelcontextprotocol/inspector", - "version": "0.16.5", + "version": "0.16.6", "license": "MIT", "workspaces": [ "client", "server", "cli" ], "dependencies": { - "@modelcontextprotocol/inspector-cli": "^0.16.5", - "@modelcontextprotocol/inspector-client": "^0.16.5", - "@modelcontextprotocol/inspector-server": "^0.16.5", + "@modelcontextprotocol/inspector-cli": "^0.16.6", + "@modelcontextprotocol/inspector-client": "^0.16.6", + "@modelcontextprotocol/inspector-server": "^0.16.6", "@modelcontextprotocol/sdk": "^1.17.5", "concurrently": "^9.2.0", "open": "^10.2.0", @@ -46,7 +46,7 @@ }, "cli": { "name": "@modelcontextprotocol/inspector-cli", - "version": "0.16.5", + "version": "0.16.6", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.17.5", @@ -69,7 +69,7 @@ }, "client": { "name": "@modelcontextprotocol/inspector-client", - "version": "0.16.5", + "version": "0.16.6", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.17.5", @@ -11746,7 +11746,7 @@ }, "server": { "name": "@modelcontextprotocol/inspector-server", - "version": "0.16.5", + "version": "0.16.6", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.17.5",
server/package.json+1 −1 modified@@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-server", - "version": "0.16.5", + "version": "0.16.6", "description": "Server-side application for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)",
650f3090d263fix: add validation for OAuth redirect URLs
5 files changed · +197 −12
client/src/components/AuthDebugger.tsx+17 −0 modified@@ -6,6 +6,7 @@ import { AuthDebuggerState, EMPTY_DEBUGGER_STATE } from "../lib/auth-types"; import { OAuthFlowProgress } from "./OAuthFlowProgress"; import { OAuthStateMachine } from "../lib/oauth-state-machine"; import { SESSION_KEYS } from "../lib/constants"; +import { validateRedirectUrl } from "@/utils/urlValidation"; export interface AuthDebuggerProps { serverUrl: string; @@ -163,6 +164,22 @@ const AuthDebugger = ({ currentState.oauthStep === "authorization_code" && currentState.authorizationUrl ) { + // Validate the URL before redirecting + try { + validateRedirectUrl(currentState.authorizationUrl); + } catch (error) { + updateAuthState({ + ...currentState, + isInitiatingAuth: false, + latestError: error instanceof Error ? error : new Error(String(error)), + statusMessage: { + type: "error", + message: `Invalid authorization URL: ${error instanceof Error ? error.message : String(error)}`, + }, + }); + return; + } + // Store the current auth state before redirecting sessionStorage.setItem( SESSION_KEYS.AUTH_DEBUGGER_STATE,
client/src/components/OAuthFlowProgress.tsx+29 −6 modified@@ -4,6 +4,8 @@ import { Button } from "./ui/button"; import { DebugInspectorOAuthClientProvider } from "@/lib/auth"; import { useEffect, useMemo, useState } from "react"; import { OAuthClientInformation } from "@modelcontextprotocol/sdk/shared/auth.js"; +import { validateRedirectUrl } from "@/utils/urlValidation"; +import { useToast } from "@/lib/hooks/useToast"; interface OAuthStepProps { label: string; @@ -71,6 +73,7 @@ export const OAuthFlowProgress = ({ updateAuthState, proceedToNextStep, }: OAuthFlowProgressProps) => { + const { toast } = useToast(); const provider = useMemo( () => new DebugInspectorOAuthClientProvider(serverUrl), [serverUrl], @@ -239,16 +242,25 @@ export const OAuthFlowProgress = ({ <p className="text-xs break-all"> {authState.authorizationUrl} </p> - <a - href={authState.authorizationUrl} - target="_blank" - rel="noopener noreferrer" + <button + onClick={() => { + try { + validateRedirectUrl(authState.authorizationUrl!); + window.open(authState.authorizationUrl!, "_blank"); + } catch (error) { + toast({ + title: "Invalid URL", + description: error instanceof Error ? error.message : "The authorization URL is not valid", + variant: "destructive", + }); + } + }} className="flex items-center text-blue-500 hover:text-blue-700" aria-label="Open authorization URL in new tab" title="Open authorization URL" > <ExternalLink className="h-4 w-4" /> - </a> + </button> </div> <p className="text-xs text-muted-foreground mt-2"> Click the link to authorize in your browser. After @@ -354,7 +366,18 @@ export const OAuthFlowProgress = ({ authState.authorizationUrl && ( <Button variant="outline" - onClick={() => window.open(authState.authorizationUrl!, "_blank")} + onClick={() => { + try { + validateRedirectUrl(authState.authorizationUrl!); + window.open(authState.authorizationUrl!, "_blank"); + } catch (error) { + toast({ + title: "Invalid URL", + description: error instanceof Error ? error.message : "The authorization URL is not valid", + variant: "destructive", + }); + } + }} > Open in New Tab </Button>
client/src/lib/auth.ts+3 −6 modified@@ -11,6 +11,7 @@ import { import { discoverAuthorizationServerMetadata } from "@modelcontextprotocol/sdk/client/auth.js"; import { SESSION_KEYS, getServerSpecificKey } from "./constants"; import { generateOAuthState } from "@/utils/oauthUtils"; +import { validateRedirectUrl } from "@/utils/urlValidation"; /** * Discovers OAuth scopes from server metadata, with preference for resource metadata scopes @@ -182,12 +183,8 @@ export class InspectorOAuthClientProvider implements OAuthClientProvider { } redirectToAuthorization(authorizationUrl: URL) { - if ( - authorizationUrl.protocol !== "http:" && - authorizationUrl.protocol !== "https:" - ) { - throw new Error("Authorization URL must be HTTP or HTTPS"); - } + // Validate the URL using the shared utility + validateRedirectUrl(authorizationUrl.href); window.location.href = authorizationUrl.href; }
client/src/utils/__tests__/urlValidation.test.ts+127 −0 added@@ -0,0 +1,127 @@ +import { validateRedirectUrl } from "../urlValidation"; + +describe("validateRedirectUrl", () => { + describe("valid URLs", () => { + it("should allow HTTP URLs", () => { + expect(() => validateRedirectUrl("http://example.com")).not.toThrow(); + }); + + it("should allow HTTPS URLs", () => { + expect(() => validateRedirectUrl("https://example.com")).not.toThrow(); + }); + + it("should allow URLs with ports", () => { + expect(() => validateRedirectUrl("https://example.com:8080")).not.toThrow(); + }); + + it("should allow URLs with paths", () => { + expect(() => validateRedirectUrl("https://example.com/path/to/auth")).not.toThrow(); + }); + + it("should allow URLs with query parameters", () => { + expect(() => validateRedirectUrl("https://example.com?param=value")).not.toThrow(); + }); + }); + + describe("invalid URLs - XSS vectors", () => { + it("should block javascript: protocol", () => { + expect(() => validateRedirectUrl("javascript:alert('XSS')")).toThrow( + "Authorization URL must be HTTP or HTTPS" + ); + }); + + it("should block javascript: with encoded characters", () => { + expect(() => validateRedirectUrl("javascript:alert%28%27XSS%27%29")).toThrow( + "Authorization URL must be HTTP or HTTPS" + ); + }); + + it("should block data: protocol", () => { + expect(() => validateRedirectUrl("data:text/html,<script>alert('XSS')</script>")).toThrow( + "Authorization URL must be HTTP or HTTPS" + ); + }); + + it("should block vbscript: protocol", () => { + expect(() => validateRedirectUrl("vbscript:msgbox")).toThrow( + "Authorization URL must be HTTP or HTTPS" + ); + }); + + it("should block file: protocol", () => { + expect(() => validateRedirectUrl("file:///etc/passwd")).toThrow( + "Authorization URL must be HTTP or HTTPS" + ); + }); + + it("should block about: protocol", () => { + expect(() => validateRedirectUrl("about:blank")).toThrow( + "Authorization URL must be HTTP or HTTPS" + ); + }); + + it("should block custom protocols", () => { + expect(() => validateRedirectUrl("custom://example")).toThrow( + "Authorization URL must be HTTP or HTTPS" + ); + }); + }); + + describe("edge cases", () => { + it("should handle malformed URLs", () => { + expect(() => validateRedirectUrl("not a url")).toThrow( + "Invalid URL: not a url" + ); + }); + + it("should handle empty string", () => { + expect(() => validateRedirectUrl("")).toThrow( + "Invalid URL: " + ); + }); + + it("should handle URLs with unicode characters", () => { + expect(() => validateRedirectUrl("https://例え.jp")).not.toThrow(); + }); + + it("should handle URLs with case variations", () => { + expect(() => validateRedirectUrl("HTTPS://EXAMPLE.COM")).not.toThrow(); + expect(() => validateRedirectUrl("HtTpS://example.com")).not.toThrow(); + }); + + it("should handle protocol-relative URLs as invalid", () => { + expect(() => validateRedirectUrl("//example.com")).toThrow( + "Invalid URL: //example.com" + ); + }); + + it("should handle URLs with authentication", () => { + expect(() => validateRedirectUrl("https://user:pass@example.com")).not.toThrow(); + }); + }); + + describe("security considerations", () => { + it("should not be fooled by whitespace", () => { + expect(() => validateRedirectUrl(" javascript:alert('XSS')")).toThrow(); + expect(() => validateRedirectUrl("javascript:alert('XSS') ")).toThrow(); + }); + + it("should handle null bytes", () => { + expect(() => validateRedirectUrl("java\x00script:alert('XSS')")).toThrow(); + }); + + it("should handle tab characters", () => { + expect(() => validateRedirectUrl("java\tscript:alert('XSS')")).toThrow(); + }); + + it("should handle newlines", () => { + expect(() => validateRedirectUrl("java\nscript:alert('XSS')")).toThrow(); + }); + + it("should handle mixed case protocols", () => { + expect(() => validateRedirectUrl("JaVaScRiPt:alert('XSS')")).toThrow( + "Authorization URL must be HTTP or HTTPS" + ); + }); + }); +}); \ No newline at end of file
client/src/utils/urlValidation.ts+21 −0 added@@ -0,0 +1,21 @@ +/** + * Validates that a URL is safe for redirection. + * Only allows HTTP and HTTPS protocols to prevent XSS attacks. + * + * @param url - The URL string to validate + * @throws Error if the URL has an unsafe protocol + */ +export function validateRedirectUrl(url: string): void { + try { + const parsedUrl = new URL(url); + if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") { + throw new Error("Authorization URL must be HTTP or HTTPS"); + } + } catch (error) { + if (error instanceof Error && error.message === "Authorization URL must be HTTP or HTTPS") { + throw error; + } + // If URL parsing fails, it's also invalid + throw new Error(`Invalid URL: ${url}`); + } +} \ No newline at end of file
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-g9hg-qhmf-q45mghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-58444ghsaADVISORY
- github.com/modelcontextprotocol/inspector/commit/650f3090d26344a672026b737d81586595bb1f60nvdWEB
- github.com/modelcontextprotocol/inspector/security/advisories/GHSA-g9hg-qhmf-q45mnvdWEB
- www.npmjs.com/package/@modelcontextprotocol/inspector/v/0.16.6ghsaWEB
News mentions
0No linked articles in our index yet.