fix: add validation for OAuth redirect URLs
- Create shared validateRedirectUrl utility to centralize URL validation - Only allow HTTP and HTTPS protocols in OAuth authorization URLs - Fix three vulnerable redirect paths that bypassed validation: - AuthDebugger.tsx: Direct window.location.href redirect - OAuthFlowProgress.tsx: window.open() without validation - OAuthFlowProgress.tsx: Direct anchor href attribute - Add comprehensive test coverage for URL validation - Update existing auth.ts to use shared validation utility This prevents malicious MCP servers from injecting javascript: or data: protocol URLs that would execute XSS attacks when users are redirected to the authorization endpoint. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
7fc3de7648
commit
650f3090d2
2 changed files with 197 additions and 12 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue