From f0480642cc21879dd56fbff2ad9c82df92295bb5 Mon Sep 17 00:00:00 2001 From: petrmarinec <54589756+petrmarinec@users.noreply.github.com> Date: Fri, 10 Apr 2026 23:31:52 +0200 Subject: [PATCH 1/2] fix: quote Vertex session filters --- .../adk/sessions/vertex_ai_session_service.py | 7 ++++++- .../sessions/test_vertex_ai_session_service.py | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/google/adk/sessions/vertex_ai_session_service.py b/src/google/adk/sessions/vertex_ai_session_service.py index c653456a25..f843446fc0 100644 --- a/src/google/adk/sessions/vertex_ai_session_service.py +++ b/src/google/adk/sessions/vertex_ai_session_service.py @@ -48,6 +48,11 @@ _USAGE_METADATA_CUSTOM_METADATA_KEY = '_usage_metadata' +def _quote_filter_literal(value: str) -> str: + """Quotes filter values so embedded metacharacters stay inside the literal.""" + return json.dumps(value) + + def _set_internal_custom_metadata( metadata_dict: dict[str, Any], *, key: str, value: dict[str, Any] ) -> None: @@ -228,7 +233,7 @@ async def list_sessions( sessions = [] config = {} if user_id is not None: - config['filter'] = f'user_id="{user_id}"' + config['filter'] = f'user_id={_quote_filter_literal(user_id)}' sessions_iterator = await api_client.agent_engines.sessions.list( name=f'reasoningEngines/{reasoning_engine_id}', config=config, diff --git a/tests/unittests/sessions/test_vertex_ai_session_service.py b/tests/unittests/sessions/test_vertex_ai_session_service.py index 80a7c9c537..1872f23053 100644 --- a/tests/unittests/sessions/test_vertex_ai_session_service.py +++ b/tests/unittests/sessions/test_vertex_ai_session_service.py @@ -13,6 +13,7 @@ # limitations under the License. import copy import datetime +import json import re import types from typing import Any @@ -374,6 +375,7 @@ def __init__(self) -> None: self.agent_engines.sessions.events.list.side_effect = self._list_events self.agent_engines.sessions.events.append.side_effect = self._append_event self.last_create_session_config: dict[str, Any] = {} + self.last_list_sessions_config: dict[str, Any] = {} async def __aenter__(self): """Enters the asynchronous context.""" @@ -390,6 +392,7 @@ async def _get_session(self, name: str): raise api_core_exceptions.NotFound(f'Session not found: {session_id}') async def _list_sessions(self, name: str, config: dict[str, Any]): + self.last_list_sessions_config = config filter_val = config.get('filter', '') user_id_match = re.search(r'user_id="([^"]+)"', filter_val) if user_id_match: @@ -876,6 +879,20 @@ async def test_list_sessions_all_users(): } +@pytest.mark.asyncio +@pytest.mark.usefixtures('mock_get_api_client') +async def test_list_sessions_quotes_user_id_filter(mock_api_client_instance): + session_service = mock_vertex_ai_session_service() + payload = 'attacker" OR user_id!=""' + + sessions = await session_service.list_sessions(app_name='123', user_id=payload) + + assert sessions.sessions == [] + assert mock_api_client_instance.last_list_sessions_config == { + 'filter': f'user_id={json.dumps(payload)}' + } + + @pytest.mark.asyncio @pytest.mark.usefixtures('mock_get_api_client') async def test_create_session(): From e8b8a8bf2c8e6abe219cb0599a796b62879e49fd Mon Sep 17 00:00:00 2001 From: petrmarinec <54589756+petrmarinec@users.noreply.github.com> Date: Sat, 11 Apr 2026 12:39:55 +0200 Subject: [PATCH 2/2] test: cover Vertex filter literal edge cases --- .../adk/sessions/vertex_ai_session_service.py | 3 ++- .../sessions/test_vertex_ai_session_service.py | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/google/adk/sessions/vertex_ai_session_service.py b/src/google/adk/sessions/vertex_ai_session_service.py index f843446fc0..4bdaba398a 100644 --- a/src/google/adk/sessions/vertex_ai_session_service.py +++ b/src/google/adk/sessions/vertex_ai_session_service.py @@ -50,7 +50,8 @@ def _quote_filter_literal(value: str) -> str: """Quotes filter values so embedded metacharacters stay inside the literal.""" - return json.dumps(value) + escaped_value = value.replace('\\', '\\\\').replace('"', '\\"') + return f'"{escaped_value}"' def _set_internal_custom_metadata( diff --git a/tests/unittests/sessions/test_vertex_ai_session_service.py b/tests/unittests/sessions/test_vertex_ai_session_service.py index 1872f23053..5f77f46a50 100644 --- a/tests/unittests/sessions/test_vertex_ai_session_service.py +++ b/tests/unittests/sessions/test_vertex_ai_session_service.py @@ -13,7 +13,6 @@ # limitations under the License. import copy import datetime -import json import re import types from typing import Any @@ -394,7 +393,7 @@ async def _get_session(self, name: str): async def _list_sessions(self, name: str, config: dict[str, Any]): self.last_list_sessions_config = config filter_val = config.get('filter', '') - user_id_match = re.search(r'user_id="([^"]+)"', filter_val) + user_id_match = re.search(r'user_id="((?:\\.|[^"])*)"', filter_val) if user_id_match: user_id = user_id_match.group(1) if user_id == 'user_with_pages': @@ -881,15 +880,24 @@ async def test_list_sessions_all_users(): @pytest.mark.asyncio @pytest.mark.usefixtures('mock_get_api_client') -async def test_list_sessions_quotes_user_id_filter(mock_api_client_instance): +@pytest.mark.parametrize( + ('payload', 'expected_filter'), + [ + ('attacker" OR user_id!=""', 'user_id="attacker\\" OR user_id!=\\"\\""'), + ('\\', 'user_id="\\\\"'), + ('', 'user_id=""'), + ], +) +async def test_list_sessions_quotes_user_id_filter( + mock_api_client_instance, payload, expected_filter +): session_service = mock_vertex_ai_session_service() - payload = 'attacker" OR user_id!=""' sessions = await session_service.list_sessions(app_name='123', user_id=payload) assert sessions.sessions == [] assert mock_api_client_instance.last_list_sessions_config == { - 'filter': f'user_id={json.dumps(payload)}' + 'filter': expected_filter }