1+ # Claude Debug
2+ """Test for HackerOne vulnerability report #3156202 - malformed input DOS."""
3+
4+ import anyio
5+ import pytest
6+
7+ from mcp .server .models import InitializationOptions
8+ from mcp .server .session import ServerSession
9+ from mcp .shared .message import SessionMessage
10+ from mcp .types import (
11+ INVALID_PARAMS ,
12+ JSONRPCError ,
13+ JSONRPCMessage ,
14+ JSONRPCRequest ,
15+ ServerCapabilities ,
16+ )
17+
18+
19+ @pytest .mark .anyio
20+ async def test_malformed_initialize_request_does_not_crash_server ():
21+ """
22+ Test that malformed initialize requests return proper error responses
23+ instead of crashing the server (HackerOne #3156202).
24+ """
25+ # Create in-memory streams for testing
26+ read_send_stream , read_receive_stream = anyio .create_memory_object_stream [
27+ SessionMessage | Exception
28+ ](10 )
29+ write_send_stream , write_receive_stream = anyio .create_memory_object_stream [
30+ SessionMessage
31+ ](10 )
32+
33+ try :
34+ # Create a malformed initialize request (missing required params field)
35+ malformed_request = JSONRPCRequest (
36+ jsonrpc = "2.0" ,
37+ id = "f20fe86132ed4cd197f89a7134de5685" ,
38+ method = "initialize" ,
39+ # params=None # Missing required params field
40+ )
41+
42+ # Wrap in session message
43+ request_message = SessionMessage (message = JSONRPCMessage (malformed_request ))
44+
45+ # Start a server session
46+ async with ServerSession (
47+ read_stream = read_receive_stream ,
48+ write_stream = write_send_stream ,
49+ init_options = InitializationOptions (
50+ server_name = "test_server" ,
51+ server_version = "1.0.0" ,
52+ capabilities = ServerCapabilities (),
53+ ),
54+ ):
55+ # Send the malformed request
56+ await read_send_stream .send (request_message )
57+
58+ # Give the session time to process the request
59+ await anyio .sleep (0.1 )
60+
61+ # Check that we received an error response instead of a crash
62+ try :
63+ response_message = write_receive_stream .receive_nowait ()
64+ response = response_message .message .root
65+
66+ # Verify it's a proper JSON-RPC error response
67+ assert isinstance (response , JSONRPCError )
68+ assert response .jsonrpc == "2.0"
69+ assert response .id == "f20fe86132ed4cd197f89a7134de5685"
70+ assert response .error .code == INVALID_PARAMS
71+ assert "Invalid request parameters" in response .error .message
72+
73+ # Verify the session is still alive and can handle more requests
74+ # Send another malformed request to confirm server stability
75+ another_malformed_request = JSONRPCRequest (
76+ jsonrpc = "2.0" ,
77+ id = "test_id_2" ,
78+ method = "tools/call" ,
79+ # params=None # Missing required params
80+ )
81+ another_request_message = SessionMessage (
82+ message = JSONRPCMessage (another_malformed_request )
83+ )
84+
85+ await read_send_stream .send (another_request_message )
86+ await anyio .sleep (0.1 )
87+
88+ # Should get another error response, not a crash
89+ second_response_message = write_receive_stream .receive_nowait ()
90+ second_response = second_response_message .message .root
91+
92+ assert isinstance (second_response , JSONRPCError )
93+ assert second_response .id == "test_id_2"
94+ assert second_response .error .code == INVALID_PARAMS
95+
96+ except anyio .WouldBlock :
97+ pytest .fail ("No response received - server likely crashed" )
98+ finally :
99+ # Close all streams to ensure proper cleanup
100+ await read_send_stream .aclose ()
101+ await write_send_stream .aclose ()
102+ await read_receive_stream .aclose ()
103+ await write_receive_stream .aclose ()
104+
105+
106+ @pytest .mark .anyio
107+ async def test_multiple_concurrent_malformed_requests ():
108+ """
109+ Test that multiple concurrent malformed requests don't crash the server.
110+ """
111+ # Create in-memory streams for testing
112+ read_send_stream , read_receive_stream = anyio .create_memory_object_stream [
113+ SessionMessage | Exception
114+ ](100 )
115+ write_send_stream , write_receive_stream = anyio .create_memory_object_stream [
116+ SessionMessage
117+ ](100 )
118+
119+ try :
120+ # Start a server session
121+ async with ServerSession (
122+ read_stream = read_receive_stream ,
123+ write_stream = write_send_stream ,
124+ init_options = InitializationOptions (
125+ server_name = "test_server" ,
126+ server_version = "1.0.0" ,
127+ capabilities = ServerCapabilities (),
128+ ),
129+ ):
130+ # Send multiple malformed requests concurrently
131+ malformed_requests = []
132+ for i in range (10 ):
133+ malformed_request = JSONRPCRequest (
134+ jsonrpc = "2.0" ,
135+ id = f"malformed_{ i } " ,
136+ method = "initialize" ,
137+ # params=None # Missing required params
138+ )
139+ request_message = SessionMessage (
140+ message = JSONRPCMessage (malformed_request )
141+ )
142+ malformed_requests .append (request_message )
143+
144+ # Send all requests
145+ for request in malformed_requests :
146+ await read_send_stream .send (request )
147+
148+ # Give time to process
149+ await anyio .sleep (0.2 )
150+
151+ # Verify we get error responses for all requests
152+ error_responses = []
153+ try :
154+ while True :
155+ response_message = write_receive_stream .receive_nowait ()
156+ error_responses .append (response_message .message .root )
157+ except anyio .WouldBlock :
158+ pass # No more messages
159+
160+ # Should have received 10 error responses
161+ assert len (error_responses ) == 10
162+
163+ for i , response in enumerate (error_responses ):
164+ assert isinstance (response , JSONRPCError )
165+ assert response .id == f"malformed_{ i } "
166+ assert response .error .code == INVALID_PARAMS
167+ finally :
168+ # Close all streams to ensure proper cleanup
169+ await read_send_stream .aclose ()
170+ await write_send_stream .aclose ()
171+ await read_receive_stream .aclose ()
172+ await write_receive_stream .aclose ()
0 commit comments