11// browser-provider.ts
22import { OAuthClientInformation , OAuthMetadata , OAuthTokens , OAuthClientMetadata } from '@modelcontextprotocol/sdk/shared/auth.js'
33import { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js'
4+ import { sanitizeUrl } from 'strict-url-sanitise'
45// Assuming StoredState is defined in ./types.js and includes fields for provider options
56import { StoredState } from './types.js' // Adjust path if necessary
67
@@ -29,15 +30,16 @@ export class BrowserOAuthClientProvider implements OAuthClientProvider {
2930 this . serverUrlHash = this . hashString ( serverUrl )
3031 this . clientName = options . clientName || 'MCP Browser Client'
3132 this . clientUri = options . clientUri || ( typeof window !== 'undefined' ? window . location . origin : '' )
32- this . callbackUrl =
33+ this . callbackUrl = sanitizeUrl (
3334 options . callbackUrl ||
34- ( typeof window !== 'undefined' ? new URL ( '/oauth/callback' , window . location . origin ) . toString ( ) : '/oauth/callback' )
35+ ( typeof window !== 'undefined' ? new URL ( '/oauth/callback' , window . location . origin ) . toString ( ) : '/oauth/callback' ) ,
36+ )
3537 }
3638
3739 // --- SDK Interface Methods ---
3840
3941 get redirectUrl ( ) : string {
40- return this . callbackUrl
42+ return sanitizeUrl ( this . callbackUrl )
4143 }
4244
4345 get clientMetadata ( ) : OAuthClientMetadata {
@@ -143,13 +145,16 @@ export class BrowserOAuthClientProvider implements OAuthClientProvider {
143145 authorizationUrl . searchParams . set ( 'state' , state )
144146 const authUrlString = authorizationUrl . toString ( )
145147
148+ // Sanitize the authorization URL to prevent XSS attacks
149+ const sanitizedAuthUrl = sanitizeUrl ( authUrlString )
150+
146151 // Persist the exact auth URL in case the popup fails and manual navigation is needed
147- localStorage . setItem ( this . getKey ( 'last_auth_url' ) , authUrlString )
152+ localStorage . setItem ( this . getKey ( 'last_auth_url' ) , sanitizedAuthUrl )
148153
149154 // Attempt to open the popup
150155 const popupFeatures = 'width=600,height=700,resizable=yes,scrollbars=yes,status=yes' // Make configurable if needed
151156 try {
152- const popup = window . open ( authUrlString , `mcp_auth_${ this . serverUrlHash } ` , popupFeatures )
157+ const popup = window . open ( sanitizedAuthUrl , `mcp_auth_${ this . serverUrlHash } ` , popupFeatures )
153158
154159 if ( ! popup || popup . closed || typeof popup . closed === 'undefined' ) {
155160 console . warn (
@@ -175,7 +180,8 @@ export class BrowserOAuthClientProvider implements OAuthClientProvider {
175180 * Retrieves the last URL passed to `redirectToAuthorization`. Useful for manual fallback.
176181 */
177182 getLastAttemptedAuthUrl ( ) : string | null {
178- return localStorage . getItem ( this . getKey ( 'last_auth_url' ) )
183+ const storedUrl = localStorage . getItem ( this . getKey ( 'last_auth_url' ) )
184+ return storedUrl ? sanitizeUrl ( storedUrl ) : null
179185 }
180186
181187 clearStorage ( ) : number {
0 commit comments