{"record":{"id":"29f86c28b0206d65","repo":"mem0ai/mem0","slug":"invalid-filter-key-json-stringify-key-29f86c","errorCode":null,"errorMessage":"Invalid filter key: ${JSON.stringify(key)}","messagePattern":"Invalid filter key: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"mem0-ts/src/oss/src/vector_stores/milvus.ts","lineNumber":236,"sourceCode":"  /**\n   * Filter keys are interpolated straight into the expression, so restrict them\n   * to safe identifiers (same rule as the Python provider) to block injection.\n   */\n  private static readonly SAFE_FILTER_KEY = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\n  /**\n   * Build a Milvus boolean filter expression from a flat filters object.\n   * Mirrors the Python `_create_filter` (equality only, AND-combined): validate\n   * each key, escape string values (backslash first, then double-quote), and\n   * reject value types Milvus can't compare against a scalar field.\n   */\n  private createFilter(filters?: SearchFilters): string | undefined {\n    if (!filters || Object.keys(filters).length === 0) return undefined;\n    const operands: string[] = [];\n    for (const [key, value] of Object.entries(filters)) {\n      if (value === undefined || value === null) continue;\n      if (!Milvus.SAFE_FILTER_KEY.test(key)) {\n        throw new Error(`Invalid filter key: ${JSON.stringify(key)}`);\n      }\n      if (value === \"*\") {\n        // Wildcard - match any value. Milvus has no direct wildcard, so skip\n        // the clause rather than emitting a literal `== \"*\"` that matches\n        // nothing. Mirrors the Python provider (#6187) and the chroma/pinecone\n        // stores.\n        continue;\n      }\n      if (typeof value === \"string\") {\n        // Escape backslashes before quotes so a value can't break out of the\n        // string literal (order matters, exactly as in the Python provider).\n        const escaped = value.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n        operands.push(`(metadata[\"${key}\"] == \"${escaped}\")`);\n      } else if (typeof value === \"number\" || typeof value === \"boolean\") {\n        operands.push(`(metadata[\"${key}\"] == ${value})`);\n      } else {\n        throw new Error(\n          `Filter value for ${JSON.stringify(key)} must be a string, number, or boolean, got ${typeof value}`,","sourceCodeStart":218,"sourceCodeEnd":254,"githubUrl":"https://github.com/mem0ai/mem0/blob/001c235229be8795e3834520467bd0d661ed8f34/mem0-ts/src/oss/src/vector_stores/milvus.ts#L218-L254","documentation":"createFilter() interpolates filter keys into a Milvus boolean expression as metadata[\"<key>\"], so a key containing characters outside [a-zA-Z_][a-zA-Z0-9_]* could alter or inject expression syntax. Keys are validated against SAFE_FILTER_KEY and rejected with JSON.stringify(key) so the offending key is visible in the error. This mirrors the Python provider's _create_filter.","triggerScenarios":"Passing filters with keys like 'user-id', 'user id', 'user.id', 'user:id', keys starting with a digit ('1key'), empty-string keys, or non-string keys after Object.entries coercion; derived keys built from user input containing dashes or dots.","commonSituations":"Using hyphenated or dotted metadata field names (common in JSON from external systems); slugifying filter keys to kebab-case; migrating metadata schemas from systems that allow arbitrary key characters.","solutions":["Rename filter keys to match ^[a-zA-Z_][a-zA-Z0-9_]*$ (snake_case is the repo convention: user_id, agent_id, run_id).","Map external keys to safe keys before calling search: { user_id: external['user-id'] }.","Sanitize keys at your API boundary: key.replace(/[^a-zA-Z0-9_]/g, '_') with a leading-underscore fix if it starts with a digit."],"exampleFix":"// before\nstore.search(q, 5, { 'user-id': 'u1' }); // throws: Invalid filter key\n\n// after\nstore.search(q, 5, { user_id: 'u1' });","handlingStrategy":"type-guard","validationCode":"const SAFE_KEY = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\nconst badKeys = Object.keys(filters || {}).filter((k) => !SAFE_KEY.test(k));\nif (badKeys.length) throw new Error(`Unsafe Milvus filter keys: ${badKeys.join(', ')}`);","typeGuard":"const isSafeFilterKey = (k: string): boolean => /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(k);\nfunction assertSafeMilvusFilters(f: Record<string, unknown>): void {\n  for (const k of Object.keys(f)) if (!isSafeFilterKey(k)) throw new Error(`Invalid filter key: ${k}`);\n}","tryCatchPattern":"try { await store.search(q, 5, filters); }\ncatch (e) {\n  if (e instanceof Error && e.message.startsWith('Invalid filter key')) {\n    // sanitize keys (replace [^a-zA-Z0-9_] with '_') and retry once\n  } else throw e;\n}","preventionTips":["Use snake_case metadata keys (user_id, agent_id, run_id) — the repo-wide convention.","Map external/kebab-case keys to safe keys at your API boundary.","Reject user-supplied filter keys early with the same regex."],"tags":["milvus","filters","injection-guard","validation","vector-store"],"backgroundTag":null,"analyzedSha":"001c235229be8795e3834520467bd0d661ed8f34","analyzedAt":"2026-08-15T01:55:42.685Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}