Update module github.com/modelcontextprotocol/registry to v1.7.7 [SECURITY]#5230
Open
renovate[bot] wants to merge 1 commit intomainfrom
Open
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #5230 +/- ##
==========================================
- Coverage 67.87% 67.85% -0.02%
==========================================
Files 610 610
Lines 62405 62481 +76
==========================================
+ Hits 42356 42399 +43
- Misses 16869 16900 +31
- Partials 3180 3182 +2 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
v1.7.0→v1.7.7Warning
Some dependencies could not be looked up. Check the Dependency Dashboard for more information.
MCP Registry has open redirect via protocol-relative path in trailing-slash middleware
CVE-2026-44427 / GHSA-v8vw-gw5j-w7m6
More information
Details
Summary
The TrailingSlashMiddleware in internal/api/server.go is vulnerable to an open redirect attack. An attacker can craft a URL with a protocol-relative path (e.g., //evil.com/) that, after trailing slash removal, results in a Location header of //evil.com — which browsers interpret as an absolute URL to an external domain.
Details
The TrailingSlashMiddleware strips trailing slashes from request paths and issues a 308 Permanent Redirect to the cleaned path. However, it does not validate or sanitize the resulting path before using it as the redirect target.
When a request is made with a path like //evil.com/, the middleware processes it as follows:
PoC
curl -v https://<registry-host>//evil.com/Impact
Phishing: Attackers can abuse the trusted registry domain to redirect users to credential-harvesting pages
Malware distribution: Redirect users to sites serving malicious downloads
Trust abuse: Links originating from the official MCP Registry domain carry implicit trust
Severity
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N/E:PReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
MCP Registry's GitHub OIDC tokens are replayable across registry deployments due to shared audience
CVE-2026-44428 / GHSA-95c3-6vvw-4mrq
More information
Details
[SECURITY] registry_001 Vulnerability Report
While analyzing the code logic, an area that may lead to unintended behavior under specific conditions was discovered.
Overview
c5c4b9e8890dd5754bee889b2f1417f4fe3b5ce5cmd/publisher/commands/login.go:67-105,130-135,199-224;cmd/publisher/auth/github-oidc.go:24-38,58-75,108-165;internal/api/handlers/v0/auth/github_oidc.go:75-135,229-277,280-296mcp-publisher login github-oidc --registry <other-registry>(or equivalent publish flow) and the publisher still requests a GitHub Actions ID token with the shared audiencemcp-registry; any other registry deployment running this code can replay that token to its own/v0/auth/github-oidcendpoint and mint a publish-capable registry JWT for the same GitHub owner namespace.Root Cause
The client-side and server-side GitHub OIDC flow is bound only to a global audience string, not to the specific registry instance being targeted. On the client side, the publisher always appends
audience=mcp-registrywhen requesting the GitHub Actions ID token, regardless of the selected--registryURL. On the server side, the exchange endpoint validates only that same fixed audience and then derives publish permissions directly fromrepository_owner. As a result, a token legitimately obtained while interacting with one registry deployment remains acceptable to any other deployment that shares the same code and audience string.Source-to-Sink Chain
cmd/publisher/commands/login.go:67-105,130-135,199-224parses the user-controlled--registryflag intoflags.RegistryURL, creates aGitHubOIDCProvider, and callsauthProvider.GetToken(ctx)for the chosen authentication method.cmd/publisher/auth/github-oidc.go:24-38obtains an OIDC token and immediately exchanges it against the selected registry URL.cmd/publisher/auth/github-oidc.go:58-75buildsexchangeURL := o.registryURL + "/v0/auth/github-oidc"and posts the GitHub token to whichever registry instance was selected.cmd/publisher/auth/github-oidc.go:108-165constructsfullURL := requestURL + "&audience=mcp-registry"and therefore requests the same audience for every registry deployment.internal/api/handlers/v0/auth/github_oidc.go:75-135validates only the shared audience value passed intoValidateToken.internal/api/handlers/v0/auth/github_oidc.go:254-277callsh.validator.ValidateToken(ctx, oidcToken, "mcp-registry")and, on success, signs a new registry JWT.internal/api/handlers/v0/auth/github_oidc.go:280-296convertsclaims.RepositoryOwnerinto the publish permission patternio.github.<owner>/*, which is then embedded into the new registry JWT.Exploitation Preconditions
Risk
This breaks deployment isolation between registry instances. A token issued for one registry interaction can be replayed across trust boundaries, allowing one deployment to impersonate the same GitHub owner identity on another deployment.
Impact
An attacker-controlled or compromised registry deployment can mint a valid registry JWT on another deployment and inherit publish permissions for the victim GitHub owner namespace. In practical terms, this enables unauthorized publication or update actions for names such as
io.github.<owner>/*on the victim registry instance.Remediation
Severity
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/VC:N/VI:L/VA:N/SC:N/SI:L/SA:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
MCP Registry vulnerable to stored XSS in catalogue UI via attribute-quote breakout in publisher-controlled
websiteUrlCVE-2026-44429 / GHSA-rqv2-m695-f8j4
More information
Details
Summary
The public catalogue UI served at
GET /(fileinternal/api/handlers/v0/ui_index.html) is vulnerable to stored cross-site scripting via theserver.websiteUrlfield of any publishedserver.json. Server-side validation ininternal/validators/validators.go(validateWebsiteURL) only checks that the URL parses, is absolute, and uses thehttpsscheme; it does not reject quote characters. Client-side, the value is interpolated into a double-quotedhrefattribute viainnerHTML, using a homegrownescapeHtmlhelper that performs the standardtextContent→innerHTMLround-trip. Per the HTML serialisation algorithm, that round-trip encodes only&,<,>and U+00A0 inside text nodes — it does not encode"or'. A literal"inwebsiteUrltherefore breaks out of thehrefattribute, allowing arbitraryon*event handlers to be appended to the same<a>element. The Content-Security-Policy on/isscript-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-atwith their own GitHub account, orPOST /v0/auth/noneon a deployment that has anonymous auth enabled) can plant a poisoned record visible to every visitor of the registry homepage.Affected component
internal/validators/validators.go—validateWebsiteURL(lines 153–199)internal/api/handlers/v0/ui_index.html—toggleDetails(card, item)at line 432, thehrefattribute built aroundescapeHtml(server.websiteUrl)escapeHtmldefined atinternal/api/handlers/v0/ui_index.htmllines 494–498Proof of concept
Obtain a Registry JWT for any namespace you control (a GitHub OAuth exchange against a throwaway account suffices):
Publish a server with a poisoned
websiteUrl. The literal"is preserved end-to-end:Visit
https://registry.modelcontextprotocol.io/, search forxss-poc, click the card to expand it, then hover the Website link in the details panel. The injectedonmouseoverfires andalert(document.domain)runs on theregistry.modelcontextprotocol.ioorigin.Why server-side validation does not catch this
Go's
net/url.Parseaccepts literal"in the path component:Neither the Huma
format:"uri"annotation norvalidateWebsiteURL's scheme/IsAbstriplet rejects this string. The architecture's existing protection —repository.urlis regex-locked to^https?://(www\.)?github\.com/[\w.-]+/[\w.-]+/?$and therefore cannot contain quotes — does not extend towebsiteUrl, which has no allowlist.Why client-side
escapeHtmldoes not catch thisPer 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 forname,version,description, etc.) but unsafe inside an attribute value, which is precisely where it is invoked forhrefon lines 432 and 426.Impact
registry.modelcontextprotocol.ioorigin, the injected script can:localStorage(baseUrl,customUrl), pinning the user's subsequent reads to an attacker-controlled "Custom" base URL.connect-src *is granted).script-src 'self' 'unsafe-inline' https://cdn.tailwindcss.comdoes not block this because'unsafe-inline'permits inline event-handler attributes.Suggested remediation (any one suffices)
escapeHtmlwith an attribute-safe encoder that also escapes",', backtick, and=— the OWASP HTML attribute-encoding rule.hrefvia string templates. UsesetAttribute('href', value)instead —setAttributeis not subject to HTML tokenisation, so no breakout is possible.validateWebsiteURLto 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).'unsafe-inline'fromscript-srcafter 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.
Severity
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:P/VC:N/VI:N/VA:N/SC:N/SI:L/SA:LReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
MCP Registry has an unauthenticated SSRF: HTTP namespace verification dials 6to4 / NAT64 / site-local IPv6 addresses, bypassing private-address allowlist
CVE-2026-44430 / GHSA-r48c-v28r-pf6v
More information
Details
Summary
The Registry's HTTP-based namespace verification (
POST /v0/auth/http,POST /v0.1/auth/http) usessafeDialContext(internal/api/handlers/v0/auth/http.go:67-110) to refuse dialling private/internal addresses when fetching the well-known public-key file from a publisher-supplied domain. The blocklist (isBlockedIP, lines 125-133) relies entirely on Go stdlib'sIsLoopback / IsPrivate / IsLinkLocalUnicast / IsMulticast / IsUnspecifiedplus a manual CGNAT range. None of these cover IPv6 6to4 (2002::/16), NAT64 (64:ff9b::/96and64:ff9b:1::/48per RFC 8215), or deprecated site-local (fec0::/10) — all of which encode arbitrary IPv4 in the address bits and tunnel to RFC1918 / cloud-metadata services on dual-stack / NAT64-enabled hosts.This is the same CWE-918 SSRF class fixed in GHSA-56c3-vfp2-5qqj on
czlonkowski/n8n-mcp(CVSS 8.5 HIGH). The remediation pattern is identical: extend the blocklist with the IPv6 prefix families that embed IPv4.The endpoint is unauthenticated — it is the login flow itself — so attack complexity is low aside from the host-level routing dependency.
Affected: latest
mainHEAD23f4fdaand current productionv1.7.6deployment athttps://registry.modelcontextprotocol.io/v0/auth/http.Details
Vulnerable code
internal/api/handlers/v0/auth/http.go:125-133:Per Go source (
src/net/ip.go), the relevant stdlib helpers cover:IsLoopback::1, IPv4-mapped of 127/8 (viaTo4()fast-path)IsPrivatefc00::/7only —ip[0]&0xfe == 0xfcIsLinkLocalUnicastfe80::/10only —ip[1]&0xc0 == 0x80(NOTfec0::/10which is0xc0)IsMulticastff00::/8IsUnspecified::The Registry's blocklist therefore does not cover:
2002::/162002:a9fe:a9fe::is the 6to4 encoding of169.254.169.254(AWS / Azure metadata).2002:0a00:0001::encodes10.0.0.1. On hosts with 6to4 routing or any explicit2002::/16route, the dial reaches the embedded IPv4.64:ff9b::/9664:ff9b::a9fe:a9fetranslates to169.254.169.254on any NAT64-enabled network — which is the default in IPv6-only GKE node pools, AWS IPv6-only EC2, Azure IPv6 VMs with NAT64, and DNS64/NAT64 corporate networks.64:ff9b:1::/48fec0::/10safeDialContextresolves DNS once and dials by IP (good — pins against rebinding TOCTOU), but the IP-allowlist gate is the security boundary, and that gate is incomplete.Exposure surface
POST /v0/auth/http(andPOST /v0.1/auth/http) is registered ininternal/api/handlers/v0/auth/http.go:197-218and routed unauthenticated ininternal/api/router/v0.go:24,39:The handler builds
https://<attacker-domain>/.well-known/mcp-registry-auth(line 143) and dials via thesafeDialContext-equipped client. Thedomainparameter is taken verbatim from the unauthenticated POST body.Critical order-of-operations confirmation in
CoreAuthHandler.ExchangeToken(internal/api/handlers/v0/auth/common.go:246-265):ValidateDomainAndTimestamp(domain, timestamp)— domain format check (no IP literal, must contain dot)DecodeAndValidateSignature(signedTimestamp)— hex decodekeyFetcher(ctx, domain)← SSRF dial happens hereVerifySignatureWithKeys(...)← only AFTER fetchSo the SSRF dial fires before any signature verification. Attacker needs only a valid RFC3339 timestamp (±15s window) and any hex string for
signedTimestamp.PoC
Tested against
mainHEAD23f4fda(make dev-composeboots Registry onlocalhost:8080).Step 1 — Set up attacker DNS
Configure
attacker.examplewith the AAAA records:(Equivalent free options: a domain on Cloudflare with manual AAAA, or a
requestbin-style service with custom DNS.)Step 2 — Trigger the dial (no credentials required)
Timestamp need only be within ±15s of server clock.
signedTimestampis any hex string — it is decoded but only verified AFTERFetchKeyhas already dialled.Step 3 — Observe
On a NAT64-enabled host (default in IPv6-only GKE / AWS IPv6 nodes / Cloudflare WARP), the server-side dial reaches
169.254.169.254:443. Tcpdump on the registry host confirms the outbound TLS handshake to the embedded IPv4. Where 169.254.169.254 listens on a TLS port (most cloud metadata services do not, but kube-apiserver, internal admin panels, and bespoke IPv4 services do), the connection completes and the response (limited to 4 KiB byMaxKeyResponseSize) is consumed as a key candidate.For hosts without 6to4 / NAT64 routing, the dial fails with
no route to hostrather thanrefusing to connect to private or loopback address— proving the gate did not block. The differential error message provides a blind-SSRF oracle for probing internal services for existence / TLS port reachability.Expected behaviour after fix
isBlockedIPshould returntruefor any IPv6 address in the prefix families listed above, mirroring the n8n-mcpisPrivateOrMappedIpv6helper (GHSA-56c3-vfp2-5qqj patch). Reference implementation:Then extend the call site:
A regression test fixture should set up a stub resolver returning each of the four prefix families and assert that
safeDialContextreturns the "private/loopback" error before any dial.Impact
CWE: CWE-918 Server-Side Request Forgery (consistent with parent precedent GHSA-56c3-vfp2-5qqj).
CVSS:3.1: matching the n8n-mcp precedent (
AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:L/A:N~= 8.5 HIGH). AC = High because exploitation depends on the registry host having NAT64 or 6to4 routing — the default on IPv6-only and dual-stack cloud network plans (GKE IPv6, AWS IPv6-only EC2, Azure IPv6 VMs with NAT64) but not on plain-IPv4 deployments. Privileges = None (the endpoint is the login flow itself).For the official
https://registry.modelcontextprotocol.iodeployment specifically, this lets an unauthenticated attacker reach any IPv4 address that is routable from the registry's outbound interface — including AWS / GCP / Azure metadata services if hosted on a cloud VM with metadata enabled, internal Kubernetes API servers, internal admin panels, etc. The 4 KiB response cap (MaxKeyResponseSize) limits exfiltrated content per request but does not prevent fingerprinting / oracle attacks (status-code differential, response-length differential).Self-hosters running the registry on dual-stack / IPv6-only infrastructure are equally exposed.
Why this slipped past PR #1227
The April 29 hardening batch (commit
1201cbd, "security: fix open redirect and add small hardening") explicitly addedsafeDialContextto block "loopback, RFC1918, link-local, multicast, CGNAT, or IP-literal/single-label" addresses. The author correctly identified the IPv4 attack surface and the link-local cloud-metadata vector, but composed the blocklist from Go's per-class stdlib helpers — which collectively miss the IPv6 prefix families that embed IPv4. The same gap was caught and fixed in n8n-mcp (GHSA-56c3-vfp2-5qqj). No commits ingit log --since=2026-03-01 internal/api/handlers/v0/auth/http.goreference 6to4 / NAT64 / site-local.Credit
Reported by Matteo Panzeri (GitHub: matte1782).
Severity
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:N/SC:L/SI:N/SA:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
Release Notes
modelcontextprotocol/registry (github.com/modelcontextprotocol/registry)
v1.7.7Compare Source
What's Changed
New Contributors
Full Changelog: modelcontextprotocol/registry@v1.7.6...v1.7.7
v1.7.6Compare Source
What's Changed
Full Changelog: modelcontextprotocol/registry@v1.7.5...v1.7.6
v1.7.5Compare Source
What's Changed
Full Changelog: modelcontextprotocol/registry@v1.7.4...v1.7.5
v1.7.4Compare Source
What's Changed
Full Changelog: modelcontextprotocol/registry@v1.7.3...v1.7.4
v1.7.3Compare Source
What's Changed
Full Changelog: modelcontextprotocol/registry@v1.7.2...v1.7.3
v1.7.2Compare Source
What's Changed
Full Changelog: modelcontextprotocol/registry@v1.7.1...v1.7.2
v1.7.1Compare Source
What's Changed
Full Changelog: modelcontextprotocol/registry@v1.7.0...v1.7.1
Configuration
📅 Schedule: (UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.