From 4e448269bcf7136905b45c0ef5ac34c2254a1a3b Mon Sep 17 00:00:00 2001 From: Hrushikesh Yadav <136978914+HrushiYadav@users.noreply.github.com> Date: Mon, 22 Jun 2026 18:09:01 +0530 Subject: [PATCH] fix(mongodb): reject dict filter values to prevent NoSQL operator injection (#5748) --- mem0/vector_stores/mongodb.py | 26 ++++++++++++++++++++++++++ tests/vector_stores/test_mongodb.py | 26 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/mem0/vector_stores/mongodb.py b/mem0/vector_stores/mongodb.py index 43c61e9f0..a7738c6f2 100644 --- a/mem0/vector_stores/mongodb.py +++ b/mem0/vector_stores/mongodb.py @@ -148,6 +148,22 @@ class MongoDB(VectorStoreBase): except PyMongoError as e: logger.error(f"Error inserting data: {e}") + @staticmethod + def _validate_filter_value(key: str, value: Any) -> None: + """Reject values that could inject MongoDB query operators (e.g. $ne, $gt).""" + if isinstance(value, dict): + raise ValueError( + f"Filter value for {key!r} must be a scalar (str, int, float, bool), " + f"not a dict. Dicts may contain MongoDB query operators." + ) + if isinstance(value, list): + for item in value: + if isinstance(item, dict): + raise ValueError( + f"Filter list for {key!r} contains a dict, " + f"which may contain MongoDB query operators." + ) + def search(self, query: str, vectors: List[float], top_k=5, filters: Optional[Dict] = None) -> List[OutputData]: """ Search for similar vectors using the vector search index. @@ -167,6 +183,10 @@ class MongoDB(VectorStoreBase): logger.error(f"Index '{self.index_name}' does not exist.") return [] + if filters: + for key, value in filters.items(): + self._validate_filter_value(key, value) + results = [] try: collection = self.client[self.db_name][self.collection_name] @@ -215,6 +235,9 @@ class MongoDB(VectorStoreBase): Returns: List[OutputData]: Search results, or None if Atlas Search index is not available. """ + if filters: + for key, value in filters.items(): + self._validate_filter_value(key, value) try: collection = self.client[self.db_name][self.collection_name] search_index_name = f"{self.collection_name}_text_search_index" @@ -369,6 +392,9 @@ class MongoDB(VectorStoreBase): Returns: List[OutputData]: List of vectors. """ + if filters: + for key, value in filters.items(): + self._validate_filter_value(key, value) try: query = {} if filters: diff --git a/tests/vector_stores/test_mongodb.py b/tests/vector_stores/test_mongodb.py index 5ec96ca55..1c8bc6a9d 100644 --- a/tests/vector_stores/test_mongodb.py +++ b/tests/vector_stores/test_mongodb.py @@ -401,3 +401,29 @@ def test_list_with_no_filters(mongo_vector_fixture): assert len(results) == 1 assert len(results[0]) == 1 + + +def test_search_rejects_operator_injection(mongo_vector_fixture): + """Filter values containing MongoDB operators (dicts) must be rejected.""" + mongo_vector, mock_collection, _ = mongo_vector_fixture + mock_collection.list_search_indexes.return_value = ["test_collection_vector_index"] + + with pytest.raises(ValueError, match="not a dict"): + mongo_vector.search("q", [0.1] * 1536, top_k=2, filters={"user_id": {"$ne": ""}}) + + +def test_list_rejects_operator_injection(mongo_vector_fixture): + """list() must also reject MongoDB operator injection.""" + mongo_vector, _, _ = mongo_vector_fixture + + with pytest.raises(ValueError, match="not a dict"): + mongo_vector.list(filters={"user_id": {"$regex": ".*"}}) + + +def test_search_allows_scalar_filter_values(mongo_vector_fixture): + """Normal scalar filter values (str, int, bool) must still work.""" + mongo_vector, mock_collection, _ = mongo_vector_fixture + mock_collection.aggregate.return_value = [] + mock_collection.list_search_indexes.return_value = ["test_collection_vector_index"] + + mongo_vector.search("q", [0.1] * 1536, top_k=2, filters={"user_id": "alice", "count": 5})