{"id":"GHSA-95c3-6vvw-4mrq","summary":"MCP Registry's GitHub OIDC tokens are replayable across registry deployments due to shared audience","details":"# [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 + \"&audience=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.","aliases":["CVE-2026-44428","GO-2026-5273"],"modified":"2026-07-17T21:12:30.217218009Z","published":"2026-05-08T17:06:22Z","database_specific":{"github_reviewed_at":"2026-05-08T17:06:22Z","nvd_published_at":"2026-05-14T22:16:44Z","cwe_ids":["CWE-918"],"severity":"LOW","github_reviewed":true},"references":[{"type":"WEB","url":"https://github.com/modelcontextprotocol/registry/security/advisories/GHSA-95c3-6vvw-4mrq"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-44428"},{"type":"WEB","url":"https://github.com/modelcontextprotocol/registry/pull/1229"},{"type":"WEB","url":"https://github.com/modelcontextprotocol/registry/commit/3f89fc2b1fb34fd49f3c0e1b39e964a5c67b613f"},{"type":"PACKAGE","url":"https://github.com/modelcontextprotocol/registry"},{"type":"WEB","url":"https://github.com/modelcontextprotocol/registry/releases/tag/v1.7.6"}],"affected":[{"package":{"name":"github.com/modelcontextprotocol/registry","ecosystem":"Go","purl":"pkg:golang/github.com/modelcontextprotocol/registry"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.7.6"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/05/GHSA-95c3-6vvw-4mrq/GHSA-95c3-6vvw-4mrq.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:L/A:N"},{"type":"CVSS_V4","score":"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"}]}