diff --git a/internal/oauthex/oauth2.go b/internal/oauthex/oauth2.go index de164499..fc08c6b4 100644 --- a/internal/oauthex/oauth2.go +++ b/internal/oauthex/oauth2.go @@ -65,3 +65,21 @@ func getJSON[T any](ctx context.Context, c *http.Client, url string, limit int64 } return &t, nil } + +// checkURLScheme ensures that its argument is a valid URL with a scheme +// that prevents XSS attacks. +// See #526. +func checkURLScheme(u string) error { + if u == "" { + return nil + } + uu, err := url.Parse(u) + if err != nil { + return err + } + scheme := strings.ToLower(uu.Scheme) + if scheme == "javascript" || scheme == "data" || scheme == "vbscript" { + return fmt.Errorf("URL has disallowed scheme %q", scheme) + } + return nil +} diff --git a/internal/oauthex/resource_meta.go b/internal/oauthex/resource_meta.go index 71d52cde..95c9dd04 100644 --- a/internal/oauthex/resource_meta.go +++ b/internal/oauthex/resource_meta.go @@ -159,11 +159,11 @@ func GetProtectedResourceMetadataFromHeader(ctx context.Context, header http.Hea // getPRM makes a GET request to the given URL, and validates the response. // As part of the validation, it compares the returned resource field to wantResource. -func getPRM(ctx context.Context, url string, c *http.Client, wantResource string) (*ProtectedResourceMetadata, error) { - if !strings.HasPrefix(strings.ToUpper(url), "HTTPS://") { - return nil, fmt.Errorf("resource URL %q does not use HTTPS", url) +func getPRM(ctx context.Context, purl string, c *http.Client, wantResource string) (*ProtectedResourceMetadata, error) { + if !strings.HasPrefix(strings.ToUpper(purl), "HTTPS://") { + return nil, fmt.Errorf("resource URL %q does not use HTTPS", purl) } - prm, err := getJSON[ProtectedResourceMetadata](ctx, c, url, 1<<20) + prm, err := getJSON[ProtectedResourceMetadata](ctx, c, purl, 1<<20) if err != nil { return nil, err } @@ -171,6 +171,12 @@ func getPRM(ctx context.Context, url string, c *http.Client, wantResource string if prm.Resource != wantResource { return nil, fmt.Errorf("got metadata resource %q, want %q", prm.Resource, wantResource) } + // Validate the authorization server URLs to prevent XSS attacks (see #526). + for _, u := range prm.AuthorizationServers { + if err := checkURLScheme(u); err != nil { + return nil, err + } + } return prm, nil } diff --git a/oauthex/oauthex.go b/oauthex/oauthex.go index df326781..3c28dce9 100644 --- a/oauthex/oauthex.go +++ b/oauthex/oauthex.go @@ -5,7 +5,9 @@ // Package oauthex implements extensions to OAuth2. package oauthex -import "github.com/modelcontextprotocol/go-sdk/internal/oauthex" +import ( + "github.com/modelcontextprotocol/go-sdk/internal/oauthex" +) // ProtectedResourceMetadata is the metadata for an OAuth 2.0 protected resource, // as defined in section 2 of https://www.rfc-editor.org/rfc/rfc9728.html.