Skip to content

Validates registered redirect_uris for DCR are a secure schema with no fragments - #3206

Open
Varshith-Kali wants to merge 5 commits into
modelcontextprotocol:mainfrom
Varshith-Kali:fix/validate-redirect-uris
Open

Validates registered redirect_uris for DCR are a secure schema with no fragments#3206
Varshith-Kali wants to merge 5 commits into
modelcontextprotocol:mainfrom
Varshith-Kali:fix/validate-redirect-uris

Conversation

@Varshith-Kali

Copy link
Copy Markdown

Fixes #2629.

Summary

Dynamic Client Registration currently accepts redirect_uris with unsafe schemes (javascript:, data:, file:, ftp:) and fragments without complaint. RFC 9700 .1.1 and RFC 7591 require HTTPS for redirect_uris and prohibit fragments.

Changes

  • Added validate_redirect_uri() to routes.py, mirroring the existing validate_issuer_url() pattern: only https:// allowed, with http:// loopback exception for localhost/127.0.0.1/[::1]
  • Rejects URIs with explicit or empty fragments
  • Called in RegistrationHandler.handle() after model_validate_json, with 400 invalid_redirect_uri response on failure
  • 9 unit tests covering allowed HTTP loopback URIs and rejected unsafe schemes and fragments

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tests/server/auth/test_routes.py">

<violation number="1" location="tests/server/auth/test_routes.py:91">
P1: test_validate_redirect_uri_javascript_scheme_rejected will fail: AnyHttpUrl('javascript:alert(1)') raises a pydantic ValidationError at construction time — before validate_redirect_uri is even called — with message "URL scheme should be 'http' or 'https'", which does not match the expected pattern 'Redirect URI must use HTTPS'. Either pass a mock or skip the AnyHttpUrl wrapper for non-HTTP scheme inputs.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/mcp/server/auth/handlers/register.py Outdated
Comment thread tests/server/auth/test_routes.py Outdated


def test_validate_redirect_uri_javascript_scheme_rejected():
with pytest.raises(ValueError, match='Redirect URI must use HTTPS'):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: test_validate_redirect_uri_javascript_scheme_rejected will fail: AnyHttpUrl('javascript:alert(1)') raises a pydantic ValidationError at construction time — before validate_redirect_uri is even called — with message "URL scheme should be 'http' or 'https'", which does not match the expected pattern 'Redirect URI must use HTTPS'. Either pass a mock or skip the AnyHttpUrl wrapper for non-HTTP scheme inputs.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/server/auth/test_routes.py, line 91:

<comment>test_validate_redirect_uri_javascript_scheme_rejected will fail: AnyHttpUrl('javascript:alert(1)') raises a pydantic ValidationError at construction time — before validate_redirect_uri is even called — with message "URL scheme should be 'http' or 'https'", which does not match the expected pattern 'Redirect URI must use HTTPS'. Either pass a mock or skip the AnyHttpUrl wrapper for non-HTTP scheme inputs.</comment>

<file context>
@@ -70,3 +70,43 @@ def test_build_metadata_serves_issuer_without_trailing_slash():
+
+
+def test_validate_redirect_uri_javascript_scheme_rejected():
+    with pytest.raises(ValueError, match='Redirect URI must use HTTPS'):
+        validate_redirect_uri(AnyHttpUrl('javascript:alert(1)'))
+
</file context>

Comment thread src/mcp/server/auth/routes.py Outdated
… module

The original implementation put validate_redirect_uri in routes.py, which
caused a circular import: register.py imports from routes, and routes
imports from other modules that import back. Moving the validation
functions to a dedicated url_validators.py module breaks the cycle.

- validate_redirect_uri now lives in url_validators.py alongside
  validate_issuer_url (previously in routes.py)
- register.py imports from url_validators instead of routes
- test_routes.py updated to import from url_validators
- Ruff format fixes applied (single-line raise, double-quote match strings)
@Varshith-Kali
Varshith-Kali force-pushed the fix/validate-redirect-uris branch from 9eb6e29 to fb893dd Compare July 29, 2026 15:07
The SDK intentionally accepts non-loopback HTTP redirect URIs per
existing tests (test_a_non_loopback_http_redirect_uri_is_accepted).
Narrow scope to only reject dangerous schemes (javascript:, data:,
file:, etc.) and fragments, matching the SDK's existing policy.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tests/server/auth/test_routes.py">

<violation number="1" location="tests/server/auth/test_routes.py:102">
P1: validate_redirect_uri accepts http:// for any host, not just loopback. The docstring promises RFC 9700 HTTPS enforcement with loopback exception, but only non-HTTP(S) schemes are rejected — http://evil.com/cb passes. This exposes authorization codes to interception over plain HTTP for non-loopback redirect URIs.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

validate_redirect_uri(AnyHttpUrl("file:///etc/passwd"))


def test_validate_redirect_uri_http_non_loopback_allowed():

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: validate_redirect_uri accepts http:// for any host, not just loopback. The docstring promises RFC 9700 HTTPS enforcement with loopback exception, but only non-HTTP(S) schemes are rejected — http://evil.com/cb passes. This exposes authorization codes to interception over plain HTTP for non-loopback redirect URIs.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/server/auth/test_routes.py, line 102:

<comment>validate_redirect_uri accepts http:// for any host, not just loopback. The docstring promises RFC 9700 HTTPS enforcement with loopback exception, but only non-HTTP(S) schemes are rejected — http://evil.com/cb passes. This exposes authorization codes to interception over plain HTTP for non-loopback redirect URIs.</comment>

<file context>
@@ -99,9 +99,8 @@ def test_validate_redirect_uri_file_scheme_rejected():
-def test_validate_redirect_uri_http_non_loopback_rejected():
-    with pytest.raises(ValueError, match="Redirect URI must use HTTPS"):
-        validate_redirect_uri(AnyHttpUrl("http://evil.com/cb"))
+def test_validate_redirect_uri_http_non_loopback_allowed():
+    validate_redirect_uri(AnyHttpUrl("http://evil.com/cb"))
 
</file context>

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DCR registration accepts redirect_uris with non-HTTPS / non-loopback / fragmented schemes

1 participant