Skip to content

Commit fd0dc9d

Browse files
authored
internal/oauthex: address URL attacks in PRM (#539)
Fix Protected Resource Metadata to prevent URL attacks, as described in the issue. For #526
1 parent af4bddf commit fd0dc9d

File tree

3 files changed

+31
-5
lines changed

3 files changed

+31
-5
lines changed

internal/oauthex/oauth2.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,21 @@ func getJSON[T any](ctx context.Context, c *http.Client, url string, limit int64
6565
}
6666
return &t, nil
6767
}
68+
69+
// checkURLScheme ensures that its argument is a valid URL with a scheme
70+
// that prevents XSS attacks.
71+
// See #526.
72+
func checkURLScheme(u string) error {
73+
if u == "" {
74+
return nil
75+
}
76+
uu, err := url.Parse(u)
77+
if err != nil {
78+
return err
79+
}
80+
scheme := strings.ToLower(uu.Scheme)
81+
if scheme == "javascript" || scheme == "data" || scheme == "vbscript" {
82+
return fmt.Errorf("URL has disallowed scheme %q", scheme)
83+
}
84+
return nil
85+
}

internal/oauthex/resource_meta.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,18 +159,24 @@ func GetProtectedResourceMetadataFromHeader(ctx context.Context, header http.Hea
159159

160160
// getPRM makes a GET request to the given URL, and validates the response.
161161
// As part of the validation, it compares the returned resource field to wantResource.
162-
func getPRM(ctx context.Context, url string, c *http.Client, wantResource string) (*ProtectedResourceMetadata, error) {
163-
if !strings.HasPrefix(strings.ToUpper(url), "HTTPS://") {
164-
return nil, fmt.Errorf("resource URL %q does not use HTTPS", url)
162+
func getPRM(ctx context.Context, purl string, c *http.Client, wantResource string) (*ProtectedResourceMetadata, error) {
163+
if !strings.HasPrefix(strings.ToUpper(purl), "HTTPS://") {
164+
return nil, fmt.Errorf("resource URL %q does not use HTTPS", purl)
165165
}
166-
prm, err := getJSON[ProtectedResourceMetadata](ctx, c, url, 1<<20)
166+
prm, err := getJSON[ProtectedResourceMetadata](ctx, c, purl, 1<<20)
167167
if err != nil {
168168
return nil, err
169169
}
170170
// Validate the Resource field to thwart impersonation attacks (section 3.3).
171171
if prm.Resource != wantResource {
172172
return nil, fmt.Errorf("got metadata resource %q, want %q", prm.Resource, wantResource)
173173
}
174+
// Validate the authorization server URLs to prevent XSS attacks (see #526).
175+
for _, u := range prm.AuthorizationServers {
176+
if err := checkURLScheme(u); err != nil {
177+
return nil, err
178+
}
179+
}
174180
return prm, nil
175181
}
176182

oauthex/oauthex.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
// Package oauthex implements extensions to OAuth2.
66
package oauthex
77

8-
import "github.com/modelcontextprotocol/go-sdk/internal/oauthex"
8+
import (
9+
"github.com/modelcontextprotocol/go-sdk/internal/oauthex"
10+
)
911

1012
// ProtectedResourceMetadata is the metadata for an OAuth 2.0 protected resource,
1113
// as defined in section 2 of https://www.rfc-editor.org/rfc/rfc9728.html.

0 commit comments

Comments
 (0)