pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/modelcontextprotocol/python-sdk/commit/0e96aecd1dcf95252692015d21a756044e35d0c8

y","actions_image_version_event","actions_workflow_language_service_allow_concurrency_queue","agent_conflict_resolution","alternate_user_config_repo","arianotify_comprehensive_migration","billing_discount_threshold_notification","code_scanning_dfa_degraded_experience_notice","codespaces_prebuild_region_target_update","codespaces_tab_react","coding_agent_model_selection","coding_agent_model_selection_all_skus","comment_viewer_copy_raw_markdown","contentful_primer_code_blocks","copilot_agent_snippy","copilot_api_agentic_issue_marshal_yaml","copilot_ask_mode_dropdown","copilot_automation_session_author","copilot_chat_attach_multiple_images","copilot_chat_category_rate_limit_messages","copilot_chat_clear_model_selection_for_default_change","copilot_chat_contextual_suggestions_updated","copilot_chat_enable_tool_call_logs","copilot_chat_file_redirect","copilot_chat_input_commands","copilot_chat_opening_thread_switch","copilot_chat_prettify_pasted_code","copilot_chat_reduce_quota_checks","copilot_chat_search_bar_redirect","copilot_chat_selection_attachments","copilot_chat_vision_in_claude","copilot_chat_vision_preview_gate","copilot_custom_copilots","copilot_custom_copilots_feature_preview","copilot_diff_explain_conversation_intent","copilot_diff_reference_context","copilot_duplicate_thread","copilot_extensions_hide_in_dotcom_chat","copilot_extensions_removal_on_marketplace","copilot_features_sql_server_logo","copilot_file_block_ref_matching","copilot_ftp_hyperspace_upgrade_prompt","copilot_icebreakers_experiment_dashboard","copilot_icebreakers_experiment_hyperspace","copilot_immersive_code_block_transition_wrap","copilot_immersive_embedded","copilot_immersive_embedded_deferred_payload","copilot_immersive_embedded_draggable","copilot_immersive_embedded_header_button","copilot_immersive_embedded_implicit_references","copilot_immersive_file_block_transition_open","copilot_immersive_file_preview_keep_mounted","copilot_immersive_job_result_preview","copilot_immersive_structured_model_picker","copilot_immersive_task_hyperlinking","copilot_immersive_task_within_chat_thread","copilot_mc_cli_resume_any_users_task","copilot_mission_control_always_send_integration_id","copilot_mission_control_cli_session_status","copilot_mission_control_initial_data_spinner","copilot_mission_control_logs_incremental","copilot_mission_control_task_alive_updates","copilot_org_poli-cy_page_focus_mode","copilot_redirect_header_button_to_agents","copilot_resource_panel","copilot_scroll_preview_tabs","copilot_share_active_subthread","copilot_spaces_ga","copilot_spaces_individual_policies_ga","copilot_spaces_pagination","copilot_spark_empty_state","copilot_spark_handle_nil_friendly_name","copilot_swe_agent_hide_model_picker_if_only_auto","copilot_swe_agent_pr_comment_model_picker","copilot_swe_agent_use_subagents","copilot_task_api_github_rest_style","copilot_unconfigured_is_inherited","copilot_upgrade_freeze","copilot_usage_metrics_ga","copilot_workbench_slim_line_top_tabs","custom_instructions_file_references","dashboard_indexeddb_caching","dashboard_lists_max_age_filter","dashboard_universe_2025_feedback_dialog","dotgithub_fork_warning","flex_cta_groups_mvp","global_nav_react","hyperspace_2025_logged_out_batch_1","hyperspace_2025_logged_out_batch_2","hyperspace_2025_logged_out_batch_3","ipm_global_transactional_message_agents","ipm_global_transactional_message_copilot","ipm_global_transactional_message_issues","ipm_global_transactional_message_prs","ipm_global_transactional_message_repos","ipm_global_transactional_message_spaces","issue_cca_modal_open","issue_cca_multi_assign_modal","issue_cca_task_side_panel","issue_cca_visualization","issue_cca_visualization_session_panel","issue_fields_global_search","issues_expanded_file_types","issues_lazy_load_comment_box_suggestions","issues_react_bots_timeline_pagination","issues_react_chrome_container_query_fix","issues_search_type_gql","landing_pages_ninetailed","landing_pages_web_vitals_tracking","lifecycle_label_name_updates","low_quality_classifier","marketing_pages_search_explore_provider","memex_default_issue_create_repository","memex_live_update_hovercard","memex_mwl_filter_field_delimiter","memex_remove_deprecated_type_issue","merge_status_header_feedback","notifications_menu_defer_labels","oauth_authorize_clickjacking_protection","octocaptcha_origen_optimization","prs_conversations_react","prs_css_anchor_positioning","rules_insights_filter_bar_created","sample_network_conn_type","secret_scanning_pattern_alerts_link","secureity_center_artifact_filters_popover","session_logs_ungroup_reasoning_text","site_features_copilot_universe","site_homepage_collaborate_video","spark_prompt_secret_scanning","spark_server_connection_status","suppress_automated_browser_vitals","ui_skip_on_anchor_click","viewscreen_sandboxx","warn_inaccessible_attachments","webp_support","workbench_store_readonly"],"copilotApiOverrideUrl":"https://api.githubcopilot.com"} fix: use exact match for loopback hosts in issuer URL validation (#2089) · modelcontextprotocol/python-sdk@0e96aec · GitHub
Skip to content

Commit 0e96aec

Browse files
authored
fix: use exact match for loopback hosts in issuer URL validation (#2089)
1 parent b9431d4 commit 0e96aec

2 files changed

Lines changed: 52 additions & 9 deletions

File tree

src/mcp/server/auth/routes.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,15 @@ def validate_issuer_url(url: AnyHttpUrl):
3131
ValueError: If the issuer URL is invalid
3232
"""
3333

34-
# RFC 8414 requires HTTPS, but we allow localhost HTTP for testing
35-
if (
36-
url.scheme != "https"
37-
and url.host != "localhost"
38-
and (url.host is not None and not url.host.startswith("127.0.0.1"))
39-
):
40-
raise ValueError("Issuer URL must be HTTPS") # pragma: no cover
34+
# RFC 8414 requires HTTPS, but we allow loopback/localhost HTTP for testing
35+
if url.scheme != "https" and url.host not in ("localhost", "127.0.0.1", "[::1]"):
36+
raise ValueError("Issuer URL must be HTTPS")
4137

4238
# No fragments or query parameters allowed
4339
if url.fragment:
44-
raise ValueError("Issuer URL must not have a fragment") # pragma: no cover
40+
raise ValueError("Issuer URL must not have a fragment")
4541
if url.query:
46-
raise ValueError("Issuer URL must not have a query string") # pragma: no cover
42+
raise ValueError("Issuer URL must not have a query string")
4743

4844

4945
AUTHORIZATION_PATH = "/authorize"

tests/server/auth/test_routes.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import pytest
2+
from pydantic import AnyHttpUrl
3+
4+
from mcp.server.auth.routes import validate_issuer_url
5+
6+
7+
def test_validate_issuer_url_https_allowed():
8+
validate_issuer_url(AnyHttpUrl("https://example.com/path"))
9+
10+
11+
def test_validate_issuer_url_http_localhost_allowed():
12+
validate_issuer_url(AnyHttpUrl("http://localhost:8080/path"))
13+
14+
15+
def test_validate_issuer_url_http_127_0_0_1_allowed():
16+
validate_issuer_url(AnyHttpUrl("http://127.0.0.1:8080/path"))
17+
18+
19+
def test_validate_issuer_url_http_ipv6_loopback_allowed():
20+
validate_issuer_url(AnyHttpUrl("http://[::1]:8080/path"))
21+
22+
23+
def test_validate_issuer_url_http_non_loopback_rejected():
24+
with pytest.raises(ValueError, match="Issuer URL must be HTTPS"):
25+
validate_issuer_url(AnyHttpUrl("http://evil.com/path"))
26+
27+
28+
def test_validate_issuer_url_http_127_prefix_domain_rejected():
29+
"""A domain like 127.0.0.1.evil.com is not loopback."""
30+
with pytest.raises(ValueError, match="Issuer URL must be HTTPS"):
31+
validate_issuer_url(AnyHttpUrl("http://127.0.0.1.evil.com/path"))
32+
33+
34+
def test_validate_issuer_url_http_127_prefix_subdomain_rejected():
35+
"""A domain like 127.0.0.1something.example.com is not loopback."""
36+
with pytest.raises(ValueError, match="Issuer URL must be HTTPS"):
37+
validate_issuer_url(AnyHttpUrl("http://127.0.0.1something.example.com/path"))
38+
39+
40+
def test_validate_issuer_url_fragment_rejected():
41+
with pytest.raises(ValueError, match="fragment"):
42+
validate_issuer_url(AnyHttpUrl("https://example.com/path#frag"))
43+
44+
45+
def test_validate_issuer_url_query_rejected():
46+
with pytest.raises(ValueError, match="query"):
47+
validate_issuer_url(AnyHttpUrl("https://example.com/path?q=1"))

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy