From e23a34af32c1a2159713d49026a79b3608c66910 Mon Sep 17 00:00:00 2001 From: yixi ning Date: Sat, 17 Oct 2026 15:31:31 +0000 Subject: [PATCH 1/2] fix(server): reject boolean JSON-RPC request IDs --- src/a2a/server/routes/jsonrpc_dispatcher.py | 7 ++++ .../server/routes/test_jsonrpc_dispatcher.py | 35 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/a2a/server/routes/jsonrpc_dispatcher.py b/src/a2a/server/routes/jsonrpc_dispatcher.py index 21b71e921..28f4ec5ae 100644 --- a/src/a2a/server/routes/jsonrpc_dispatcher.py +++ b/src/a2a/server/routes/jsonrpc_dispatcher.py @@ -233,6 +233,13 @@ async def handle_requests(self, request: Request) -> Response: # noqa: PLR0911, body = await request.json() if isinstance(body, dict): request_id = body.get('id') + if isinstance(request_id, bool): + return self._generate_error_response( + None, + InvalidRequestError( + message='Invalid request: id must be a string, number, or null' + ), + ) # Ensure request_id is valid for JSON-RPC response (str/int/None only) if request_id is not None and not isinstance( request_id, str | int diff --git a/tests/server/routes/test_jsonrpc_dispatcher.py b/tests/server/routes/test_jsonrpc_dispatcher.py index 5565b66de..ca2a6d1be 100644 --- a/tests/server/routes/test_jsonrpc_dispatcher.py +++ b/tests/server/routes/test_jsonrpc_dispatcher.py @@ -94,6 +94,41 @@ def _make_send_message_request( } +class TestJsonRpcRequestIds: + """JSON booleans are not numeric JSON-RPC request identifiers.""" + + @pytest.mark.parametrize('request_id', [True, False]) + def test_boolean_id_is_rejected( + self, client: TestClient, mock_handler: AsyncMock, request_id: bool + ) -> None: + request = _make_send_message_request() + request['id'] = request_id + + response = client.post('/', json=request) + body = response.json() + + assert response.status_code == 200 + assert body['id'] is None + assert body['error']['code'] == -32600 + mock_handler.on_message_send.assert_not_awaited() + + @pytest.mark.parametrize('request_id', [0, 1, '', 'request']) + def test_integer_and_string_ids_are_preserved( + self, client: TestClient, mock_handler: AsyncMock, request_id: int | str + ) -> None: + request = _make_send_message_request() + request['id'] = request_id + + response = client.post('/', json=request) + body = response.json() + + assert response.status_code == 200 + assert body['id'] == request_id + assert type(body['id']) is type(request_id) + assert 'result' in body + mock_handler.on_message_send.assert_awaited_once() + + class TestUnrecognizedFields: """Unrecognized fields are ignored, for forward compatibility.""" From 2270a2fa74c282ee0108a1716e2b3023bd656b50 Mon Sep 17 00:00:00 2001 From: yixi ning Date: Thu, 22 Oct 2026 18:58:07 +0800 Subject: [PATCH 2/2] fix(server): address review on boolean JSON-RPC request IDs Move the bool-id rejection into the base JSON-RPC validation step and say integer instead of number in the error message, per review on #1350. --- src/a2a/server/routes/jsonrpc_dispatcher.py | 22 +++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/a2a/server/routes/jsonrpc_dispatcher.py b/src/a2a/server/routes/jsonrpc_dispatcher.py index 28f4ec5ae..558142dff 100644 --- a/src/a2a/server/routes/jsonrpc_dispatcher.py +++ b/src/a2a/server/routes/jsonrpc_dispatcher.py @@ -233,16 +233,11 @@ async def handle_requests(self, request: Request) -> Response: # noqa: PLR0911, body = await request.json() if isinstance(body, dict): request_id = body.get('id') - if isinstance(request_id, bool): - return self._generate_error_response( - None, - InvalidRequestError( - message='Invalid request: id must be a string, number, or null' - ), - ) - # Ensure request_id is valid for JSON-RPC response (str/int/None only) - if request_id is not None and not isinstance( - request_id, str | int + # Ensure request_id is valid for JSON-RPC response (str/int/None only; + # exclude bool since bool is a subclass of int in Python) + if isinstance(request_id, bool) or ( + request_id is not None + and not isinstance(request_id, str | int) ): request_id = None logger.debug('Request body: %s', body) @@ -264,6 +259,13 @@ async def handle_requests(self, request: Request) -> Response: # noqa: PLR0911, message="Invalid request: 'jsonrpc' must be exactly '2.0'" ), ) + if isinstance(base_request._id, bool): # noqa: SLF001 + return self._generate_error_response( + request_id, + InvalidRequestError( + message='Invalid request: id must be a string, integer, or null' + ), + ) except Exception as e: logger.exception('Failed to validate base JSON-RPC request') return self._generate_error_response(