fix(validators): reject HTML metacharacters and whitespace in websiteUrl (#1249)
`validateWebsiteURL` only checked that the URL parses, is absolute, and uses the `https` scheme. It accepted literal `"`, `'`, `<`, `>`, and ASCII space — none of which are valid in a URI per RFC 3986, and all of which broke rendering when the value flowed into the catalogue UI's `<a href="...">` template. Add a `strings.IndexAny` check after the scheme check that rejects any of these characters with a clear `website-url-invalid-characters` issue that points at the offending byte position. Control characters (`\t`, `\n`, `\r`) are also covered: they're caught one step earlier by Go's `url.Parse`, but the bytes are listed in the rejection set so the behaviour is explicit and survives any future relaxation of `url.Parse`. Already-percent-encoded URLs (e.g. `?q=hello%20world`) continue to validate cleanly. Adds table-driven cases for `"`, `'`, `<>`, space, newline, and a positive case for percent-encoded special characters. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
fbfd63ced8
commit
78b7bbde07
2 changed files with 108 additions and 0 deletions
|
|
@ -195,6 +195,20 @@ func validateWebsiteURL(ctx *ValidationContext, websiteURL string) *ValidationRe
|
|||
result.AddIssue(issue)
|
||||
}
|
||||
|
||||
// Reject characters that aren't valid in a URI per RFC 3986 and that have
|
||||
// caused rendering issues when websiteUrl flows into the catalogue UI's
|
||||
// href attributes. Publishers should percent-encode these in the source URL.
|
||||
if i := strings.IndexAny(websiteURL, "\"'<> \t\n\r"); i >= 0 {
|
||||
issue := NewValidationIssue(
|
||||
ValidationIssueTypeSemantic,
|
||||
ctx.String(),
|
||||
fmt.Sprintf("websiteUrl contains an invalid character %q at position %d: %s", websiteURL[i], i, websiteURL),
|
||||
ValidationIssueSeverityError,
|
||||
"website-url-invalid-characters",
|
||||
)
|
||||
result.AddIssue(issue)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import (
|
|||
"github.com/modelcontextprotocol/registry/pkg/model"
|
||||
)
|
||||
|
||||
const websiteURLInvalidCharErrSubstr = "websiteUrl contains an invalid character"
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
@ -486,6 +488,98 @@ func TestValidate(t *testing.T) {
|
|||
},
|
||||
expectedError: "invalid websiteUrl:",
|
||||
},
|
||||
{
|
||||
name: "server with websiteUrl containing double quote",
|
||||
serverDetail: apiv0.ServerJSON{
|
||||
Schema: model.CurrentSchemaURL,
|
||||
Name: "com.example/test-server",
|
||||
Description: "A test server",
|
||||
Repository: &model.Repository{
|
||||
URL: "https://github.com/owner/repo",
|
||||
Source: "github",
|
||||
},
|
||||
Version: "1.0.0",
|
||||
WebsiteURL: `https://example.com/"oops`,
|
||||
},
|
||||
expectedError: websiteURLInvalidCharErrSubstr,
|
||||
},
|
||||
{
|
||||
name: "server with websiteUrl containing single quote",
|
||||
serverDetail: apiv0.ServerJSON{
|
||||
Schema: model.CurrentSchemaURL,
|
||||
Name: "com.example/test-server",
|
||||
Description: "A test server",
|
||||
Repository: &model.Repository{
|
||||
URL: "https://github.com/owner/repo",
|
||||
Source: "github",
|
||||
},
|
||||
Version: "1.0.0",
|
||||
WebsiteURL: "https://example.com/it's",
|
||||
},
|
||||
expectedError: websiteURLInvalidCharErrSubstr,
|
||||
},
|
||||
{
|
||||
name: "server with websiteUrl containing angle brackets",
|
||||
serverDetail: apiv0.ServerJSON{
|
||||
Schema: model.CurrentSchemaURL,
|
||||
Name: "com.example/test-server",
|
||||
Description: "A test server",
|
||||
Repository: &model.Repository{
|
||||
URL: "https://github.com/owner/repo",
|
||||
Source: "github",
|
||||
},
|
||||
Version: "1.0.0",
|
||||
WebsiteURL: "https://example.com/<x>",
|
||||
},
|
||||
expectedError: websiteURLInvalidCharErrSubstr,
|
||||
},
|
||||
{
|
||||
name: "server with websiteUrl containing space",
|
||||
serverDetail: apiv0.ServerJSON{
|
||||
Schema: model.CurrentSchemaURL,
|
||||
Name: "com.example/test-server",
|
||||
Description: "A test server",
|
||||
Repository: &model.Repository{
|
||||
URL: "https://github.com/owner/repo",
|
||||
Source: "github",
|
||||
},
|
||||
Version: "1.0.0",
|
||||
WebsiteURL: "https://example.com/has space",
|
||||
},
|
||||
expectedError: websiteURLInvalidCharErrSubstr,
|
||||
},
|
||||
{
|
||||
name: "server with websiteUrl containing newline",
|
||||
serverDetail: apiv0.ServerJSON{
|
||||
Schema: model.CurrentSchemaURL,
|
||||
Name: "com.example/test-server",
|
||||
Description: "A test server",
|
||||
Repository: &model.Repository{
|
||||
URL: "https://github.com/owner/repo",
|
||||
Source: "github",
|
||||
},
|
||||
Version: "1.0.0",
|
||||
WebsiteURL: "https://example.com/has\nnewline",
|
||||
},
|
||||
// url.Parse rejects ASCII control chars before our character-set
|
||||
// check runs; either path produces a validation error.
|
||||
expectedError: "invalid websiteUrl:",
|
||||
},
|
||||
{
|
||||
name: "server with websiteUrl with percent-encoded special chars is accepted",
|
||||
serverDetail: apiv0.ServerJSON{
|
||||
Schema: model.CurrentSchemaURL,
|
||||
Name: "com.example/test-server",
|
||||
Description: "A test server",
|
||||
Repository: &model.Repository{
|
||||
URL: "https://github.com/owner/repo",
|
||||
Source: "github",
|
||||
},
|
||||
Version: "1.0.0",
|
||||
WebsiteURL: "https://example.com/path/?q=hello%20world&r=a%22b",
|
||||
},
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "server with websiteUrl that matches namespace domain",
|
||||
serverDetail: apiv0.ServerJSON{
|
||||
|
|
|
|||
Loading…
Reference in a new issue