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