{"record":{"id":"c1b861841cf02bf8","repo":"mem0ai/mem0","slug":"invalid-filter-key-key-r-c1b861","errorCode":null,"errorMessage":"Invalid filter key: {key!r}","messagePattern":"Invalid filter key: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"mem0/vector_stores/opensearch.py","lineNumber":24,"sourceCode":"try:\n    from opensearchpy import OpenSearch, RequestsHttpConnection\nexcept ImportError:\n    raise ImportError(\"OpenSearch requires extra dependencies. Install with `pip install opensearch-py`\") from None\n\nfrom pydantic import BaseModel\n\nfrom mem0.configs.vector_stores.opensearch import OpenSearchConfig\nfrom mem0.vector_stores.base import VectorStoreBase\n\nlogger = logging.getLogger(__name__)\n\n_SAFE_FILTER_KEY = re.compile(r\"^[a-zA-Z_][a-zA-Z0-9_.]*$\")\n_IDENTITY_FILTER_KEYS = (\"user_id\", \"agent_id\", \"run_id\")\n\n\ndef _validate_filter(key: str, value) -> None:\n    if not isinstance(key, str) or not _SAFE_FILTER_KEY.match(key):\n        raise ValueError(f\"Invalid filter key: {key!r}\")\n    if not isinstance(value, (str, int, float, bool)):\n        raise ValueError(\n            f\"Filter value for {key!r} must be str, int, float, or bool, \"\n            f\"got {type(value).__name__}\"\n        )\n\n\ndef _build_filter_clauses(filters):\n    \"\"\"Build term clauses from every filter key, not just the identity keys.\"\"\"\n    filter_clauses = []\n    for key, value in (filters or {}).items():\n        if value is None:\n            continue\n        if value == \"*\":\n            # \"Any value\" wildcard (a documented Platform pattern): match\n            # documents where the field exists — as opensearch.ts already\n            # does for every key — instead of a literal, near-always-empty\n            # term match on the string \"*\".","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/mem0ai/mem0/blob/001c235229be8795e3834520467bd0d661ed8f34/mem0/vector_stores/opensearch.py#L6-L42","documentation":"OpenSearch filter keys become term-clause field names, so they are validated against ^[a-zA-Z_][a-zA-Z0-9_.]*$. This error means a key is not a string or contains characters outside that set (leading digit, dash, space, $, unicode). It blocks malformed DSL injection into the query body.","triggerScenarios":"filters={\"user-id\": \"u\"}, {\"2app\": 1}, {\"user id\": \"u\"}, or a numeric key from parsed JSON passed to search/list on the OpenSearch backend.","commonSituations":"Sharing filter dicts across backends where one accepted dashes; forwarding raw user input as filter keys; templating filter keys from display labels with spaces.","solutions":["Rename keys to match [a-zA-Z_][a-zA-Z0-9_.]* — e.g. user_id, app.version is allowed here (dots OK)","Whitelist filter keys at your API boundary","Store unmatchable keys inside the payload under a sanitized alias"],"exampleFix":"// before\nfilters = {\"user-id\": \"alice\"}\n\n// after\nfilters = {\"user_id\": \"alice\"}","handlingStrategy":"validation","validationCode":"import re\nSAFE_KEY = re.compile(r\"^[a-zA-Z_][a-zA-Z0-9_.]*$\")\n\ndef sanitize_os_keys(filters: dict) -> dict:\n    return {re.sub(r\"[^a-zA-Z0-9_.]\", \"_\", k): v for k, v in (filters or {}).items()\n            if isinstance(k, str) and k}\n\nfilters = sanitize_os_keys(filters)","typeGuard":"def is_os_safe_key(k) -> bool:\n    return isinstance(k, str) and bool(re.match(r\"^[a-zA-Z_][a-zA-Z0-9_.]*$\", k))","tryCatchPattern":"try:\n    store.search(q, vec, filters=filters)\nexcept ValueError as e:\n    if \"Invalid filter key\" in str(e):\n        filters = sanitize_os_keys(filters)\n        store.search(q, vec, filters=filters)\n    else:\n        raise","preventionTips":["Use snake_case (dots allowed) filter keys","Validate inbound filter keys against a whitelist","Keep one canonical filter schema even when multiple vector backends are supported"],"tags":["opensearch","filters","validation","security"],"backgroundTag":null,"analyzedSha":"001c235229be8795e3834520467bd0d661ed8f34","analyzedAt":"2026-08-15T01:55:42.685Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}