Skip to content

[v1.x] fix(auth): reject non-loopback http and fragment redirect URIs at registration - #3143

Open
samuelmbabhazi wants to merge 2 commits into
modelcontextprotocol:v1.xfrom
samuelmbabhazi:fix/register-redirect-uri-validation
Open

samuelmbabhazi wants to merge 2 commits into
modelcontextprotocol:v1.xfrom
samuelmbabhazi:fix/register-redirect-uri-validation

Conversation

@samuelmbabhazi

@samuelmbabhazi samuelmbabhazi commented Nov 1, 2026 •

Copy link
Copy Markdown

Fixes #3142.

The registration handler of mcpAuthRouter validated redirect_uris only with SafeUrlSchema, so it registered http://attacker.example.com/callback and https://client.example.com/callback#fragment with 201. The spec requires HTTPS or a loopback host for redirect URIs (Communication Security), and RFC 6749 §3.1.2 forbids a fragment.

The handler now answers both with 400 invalid_redirect_uri (RFC 7591 §3.2.2). This removes the known failure of hosting:auth:as:redirect-uri-scheme in test/e2e/requirements.ts, and the scenario now asserts that code and also registers a URI with a fragment.

  • The check lives in the handler, not in OAuthClientMetadataSchema, which clients also use to parse metadata.
  • Loopback means the hosts the authorize handler already relaxes ports for (localhost, 127.0.0.1, [::1]), so LOOPBACK_HOSTS is exported from authorize.ts and reused rather than defined twice.
  • Private-use schemes such as com.example.app:/callback are still accepted. No new option.

The changeset says what breaks: re-registering a non-loopback http: or fragment URI now fails, existing registrations are untouched.

Unit tests in register.test.ts cover the rejected cases, loopback http: on any port, and a private-use scheme. Without the change, the two rejection tests fail and the e2e scenario fails once the known failure is removed.

…istration

The registration handler validated redirect_uris only with SafeUrlSchema, so
it registered http://attacker.example.com/callback and URIs with a fragment.
The MCP authorization spec requires HTTPS or a loopback host, and RFC 6749
§3.1.2 forbids a fragment. Both now get 400 invalid_client_metadata, which is
what the hosting:auth:as:redirect-uri-scheme requirement already expected, so
its known failure is removed and the scenario also covers a fragment.

The loopback hosts are the ones the authorize handler already relaxes ports
for (localhost, 127.0.0.1, [::1]). Private-use schemes stay accepted.

Fixes modelcontextprotocol#3142
@samuelmbabhazi
samuelmbabhazi requested review from a team as code owners November 1, 2026 19:18
@changeset-bot

changeset-bot Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 353427c

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@modelcontextprotocol/sdk@3143

commit: 353427c

@JosephDoUrden JosephDoUrden left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pulled b65b1ae onto the v1.x tip and ran it: register.test.ts 17/17, server auth + client auth + shared 616/616 across 20 files, hosting-express e2e 17/17 with the known failure gone. eslint, prettier and tsc clean. merge against v1.x is a no-op.

also poked the handler with a few extra inputs. HTTP://attacker.example/cb, http://localhost.evil.com/cb, http://[::ffff:127.0.0.1]/cb and myapp://cb#frag all get 400. http://127.0.0.1:0/cb, http://[::1]:8080/cb, http://0x7f.0.0.1/cb (the url parser folds it to 127.0.0.1) and com.example.app:/cb get 201. that matches rfc 8252 §7.3 loopback and the mcp communication-security rule.

one thing on the error code. src/server/auth/handlers/register.ts:92 throws InvalidClientMetadataError, so the body says invalid_client_metadata. rfc 7591 §3.2.2 has a dedicated code for this case, invalid_redirect_uri ("the value of one or more redirection URIs is invalid"), and #3142 asks for that one.

the parallel patch for server-legacy in the issue thread uses it too, so v1.x and main would answer the same bad input with different codes.

the e2e requirement already expects invalid_client_metadata, so this is a maintainer call rather than a blocker. if they want 7591, it is a small error class in errors.ts plus the three assertions.

looks good to me otherwise.

@samuelmbabhazi

Copy link
Copy Markdown
Author

Kept invalid_client_metadata because the existing e2e requirement (hosting-express.test.ts:628) already expects it for this case. Switching to invalid_redirect_uri is a small error class plus the assertions; happy to do it if the maintainers prefer RFC 7591's dedicated code.

RFC 7591 section 3.2.2 has a dedicated code for this case, and modelcontextprotocol#3142 asks
for it. CustomOAuthError keeps the change free of new public API on v1.x.
@samuelmbabhazi

Copy link
Copy Markdown
Author

Changed my mind on the error code: it now answers invalid_redirect_uri in 353427c, as RFC 7591 §3.2.2 defines and #3142 asks. It goes through the existing CustomOAuthError, so v1.x gets no new public API. The e2e scenario asserts the new code.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v1 Issues / PRs related to v1.x

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants