fix(mongodb): reject dict filter values to prevent NoSQL operator injection (#5748)
This commit is contained in:
@@ -148,6 +148,22 @@ class MongoDB(VectorStoreBase):
|
|||||||
except PyMongoError as e:
|
except PyMongoError as e:
|
||||||
logger.error(f"Error inserting data: {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]:
|
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.
|
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.")
|
logger.error(f"Index '{self.index_name}' does not exist.")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
if filters:
|
||||||
|
for key, value in filters.items():
|
||||||
|
self._validate_filter_value(key, value)
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
try:
|
try:
|
||||||
collection = self.client[self.db_name][self.collection_name]
|
collection = self.client[self.db_name][self.collection_name]
|
||||||
@@ -215,6 +235,9 @@ class MongoDB(VectorStoreBase):
|
|||||||
Returns:
|
Returns:
|
||||||
List[OutputData]: Search results, or None if Atlas Search index is not available.
|
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:
|
try:
|
||||||
collection = self.client[self.db_name][self.collection_name]
|
collection = self.client[self.db_name][self.collection_name]
|
||||||
search_index_name = f"{self.collection_name}_text_search_index"
|
search_index_name = f"{self.collection_name}_text_search_index"
|
||||||
@@ -369,6 +392,9 @@ class MongoDB(VectorStoreBase):
|
|||||||
Returns:
|
Returns:
|
||||||
List[OutputData]: List of vectors.
|
List[OutputData]: List of vectors.
|
||||||
"""
|
"""
|
||||||
|
if filters:
|
||||||
|
for key, value in filters.items():
|
||||||
|
self._validate_filter_value(key, value)
|
||||||
try:
|
try:
|
||||||
query = {}
|
query = {}
|
||||||
if filters:
|
if filters:
|
||||||
|
|||||||
@@ -401,3 +401,29 @@ def test_list_with_no_filters(mongo_vector_fixture):
|
|||||||
|
|
||||||
assert len(results) == 1
|
assert len(results) == 1
|
||||||
assert len(results[0]) == 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})
|
||||||
|
|||||||
Reference in New Issue
Block a user