CVE-2026-44429: MCP Registry: Stored XSS in catalogue UI via attribute-quote breakout in publisher-controlled `websiteUrl`

Published May 8, 2026
·
Updated

## Summary The public catalogue UI served at `GET /` (file `internal/api/handlers/v0/ui_index.html`) is vulnerable to stored cross-site scripting via the `server.websiteUrl` field of any published `server.json`. Server-side validation in `internal/validators/validators.go` (`validateWebsiteURL`) only checks that the URL parses, is absolute, and uses the `https` scheme; it does not reject quote characters. Client-side, the value is interpolated into a double-quoted `href` attribute via `innerHTML`, using a homegrown `escapeHtml` helper that performs the standard `textContent` → `innerHTML` round-trip. Per the HTML serialisation algorithm, that round-trip encodes only `&`, `<`, `>` and U+00A0 inside text nodes — it does **not** encode `"` or `'`. A literal `"` in `websiteUrl` therefore breaks out of the `href` attribute, allowing arbitrary `on*` event handlers to be appended to the same `<a>` element. The Content-Security-Policy on `/` is `script-src 'self' 'unsafe-inline' https://cdn.tailwindcss.com`, so the injected event handlers execute. Any user able to obtain a publish token (e.g. via `POST /v0/auth/github-at` with their own GitHub account, or `POST /v0/auth/none` on a deployment that has anonymous auth enabled) can plant a poisoned record visible to every visitor of the registry homepage. ## Affected component - Validator: `internal/validators/validators.go` — `validateWebsiteURL` (lines 153–199) - Sink: `internal/api/handlers/v0/ui_index.html` — `toggleDetails(card, item)` at line 432, the `href` attribute built around `escapeHtml(server.websiteUrl)` - Helper: `escapeHtml` defined at `internal/api/handlers/v0/ui_index.html` lines 494–498 ## Proof of concept 1. Obtain a Registry JWT for any namespace you control (a GitHub OAuth exchange against a throwaway account suffices): ```bash TOKEN=$(curl -sS -X POST https://registry.modelcontextprotocol.io/v0/auth/github-at \ -H 'Content-Type: application/json' \ -d '{"github_token":"<gh-pat>"}' | jq -r .registry_token) ``` 2. Publish a server with a poisoned `websiteUrl`. The literal `"` is preserved end-to-end: ```bash curl -sS -X POST https://registry.modelcontextprotocol.io/v0/publish \ -H "Authorization: Bearer $TOKEN" \ -H 'Content-Type: application/json' \ --data-binary @- <<'EOF' { "$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", "name": "io.github.<your-account>/xss-poc", "version": "0.0.1", "description": "hover the website link", "websiteUrl": "https://example.com/\"onmouseover=alert(document.domain)//" } EOF ``` 3. Visit `https://registry.modelcontextprotocol.io/`, search for `xss-poc`, click the card to expand it, then hover the **Website** link in the details panel. The injected `onmouseover` fires and `alert(document.domain)` runs on the `registry.modelcontextprotocol.io` origin. ## Why server-side validation does not catch this Go's `net/url.Parse` accepts literal `"` in the path component: ``` input="https://example.com/\"onmouseover=alert(1)//" IsAbs=true Scheme="https" Path="/\"onmouseover=alert(1)//" ``` Neither the Huma `format:"uri"` annotation nor `validateWebsiteURL`'s scheme/`IsAbs` triplet rejects this string. The architecture's existing protection — `repository.url` is regex-locked to `^https?://(www\.)?github\.com/[\w.-]+/[\w.-]+/?$` and therefore cannot contain quotes — does not extend to `websiteUrl`, which has no allowlist. ## Why client-side `escapeHtml` does not catch this ```js function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } ``` Per the HTML5 spec (§13.3 Serialising HTML fragments), the only characters encoded inside the text content of an element are `&`, `<`, `>`, and U+00A0. `"` and `'` are **not** encoded because in a text-content context they are not special. The helper is therefore safe in element-text contexts (where it is correctly used for `name`, `version`, `description`, etc.) but unsafe inside an attribute value, which is precisely where it is invoked for `href` on lines 432 and 426. ## Impact - Stored XSS on the official MCP Registry homepage. The malicious entry sits in the public catalogue alongside legitimate ones; any user expanding the entry triggers the payload. - Because the page is served on the official `registry.modelcontextprotocol.io` origin, the injected script can: - Read and overwrite `localStorage` (`baseUrl`, `customUrl`), pinning the user's subsequent reads to an attacker-controlled "Custom" base URL. - Issue any same-origin or cross-origin XHR (`connect-src *` is granted). - Phish for Registry JWTs by injecting fake auth flows on the trusted origin. - The CSP `script-src 'self' 'unsafe-inline' https://cdn.tailwindcss.com` does not block this because `'unsafe-inline'` permits inline event-handler attributes. ## Suggested remediation (any one suffices) 1. Replace the homegrown `escapeHtml` with an attribute-safe encoder that also escapes `"`, `'`, backtick, and `=` — the OWASP HTML attribute-encoding rule. 2. Avoid building the `href` via string templates. Use `setAttribute('href', value)` instead — `setAttribute` is not subject to HTML tokenisation, so no breakout is possible. 3. Tighten `validateWebsiteURL` to reject any URL whose raw bytes contain `"`, `'`, `<`, `>`, ` `, `\t`, or `\n`, or — conservatively — store the canonical re-serialised form (`parsedURL.String()` percent-encodes such characters in the path). 4. Drop `'unsafe-inline'` from `script-src` after auditing the inline scripts on the page. Approach (3) is the smallest server-side change and immediately neutralises the exploit for any new publishes; approaches (1) or (2) close the class of bug at the sink so future fields with similar patterns are safe by default.

Affected Software

2 affected componentsFixes available
go/github.com/modelcontextprotocol/registry<1.7.7
1.7.7
Lfprojects Mcp Registry<1.7.7

Event History

May 8, 2026
Advisory Published
via GitHub·05:18 PM
Data Sourced
via GitHub·05:18 PM
DescriptionWeaknessAffected Software
May 14, 2026
CVE Published
via MITRE·09:05 PM
Data Sourced
via MITRE·09:05 PM
DescriptionWeakness
Data Sourced
via NVD·09:16 PM
DescriptionSeverityWeaknessAffected Software
Free Weekly Intel

Don't miss critical vulnerabilities

Join thousands of security professionals who receive our weekly digest of trending CVEs, zero-days, and exploited vulnerabilities.

No spam. Unsubscribe anytime.

Frequently Asked Questions

1

What is the severity of CVE-2026-44429?

CVE-2026-44429 is classified as a moderate severity vulnerability due to the risk of stored cross-site scripting events.

2

How do I fix CVE-2026-44429?

To fix CVE-2026-44429, update to version 1.7.7 or later of the affected package in your project.

3

What are the potential impacts of CVE-2026-44429?

The potential impacts of CVE-2026-44429 include unauthorized script execution in users' browsers, which can lead to data theft and session hijacking.

4

What versions of the software are affected by CVE-2026-44429?

CVE-2026-44429 affects all versions prior to 1.7.7 of the GitHub repository 'github.com/modelcontextprotocol/registry'.

5

Is CVE-2026-44429 a client-side or server-side vulnerability?

CVE-2026-44429 is a server-side vulnerability that affects the handling of user input in server responses.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203