The Key Was a Query String: Mem0's Unescaped Delimiters Leaked One Tenant's Agent Memory to Another
Agent memory is becoming the crown jewels — every deployment that gives an assistant a past gives it something worth stealing. CVE-2026-116575 (GitHub advisory GHSA-5324-3w58-qpp2, CVSS 6.3), published to NVD on 26 October 2026 with the advisory following on 28 October, shows how little it takes to break the wall between tenants: Mem0's session-scope key was built by concatenating caller-controlled identifiers with raw = and & delimiters and never escaping them. Any caller who controlled a single identifier — typically just run_id — could forge a scope string byte-identical to another tenant's and read, poison, or evict that tenant's recent conversation history.
The vulnerable construction lived in _build_session_scope() in mem0/memory/main.py (and its TypeScript twin Memory.buildSessionScope()user_id, agent_id, run_id it emitted key=value pairs joined by &. The only validation, _validate_and_trim_entity_id(), rejected empty or whitespace-only strings — it never forbade the structural characters. That string then served as the exact-match key (WHERE session_scope = ?) for fetching the last raw messages and appending new ones, in both sync and async paths and through the REST server's POST /memories endpoint, which takes run_id and agent_id straight from the request body. Parameterized SQL stopped SQL injection dead, and it did not matter: the collision happens before the query, in the key itself.
One identifier is enough
The advisory's reproduction is brutally short. Tenant A calls add() with user_id="alice", run_id="proj-x" and gets scope run_id=proj-x&user_id=alice. An attacker who controls only run_id calls add() with run_id="proj-x&user_id=alice" and gets the identical string. From there the attacker's next add() does two things at once: it reads the victim's raw prior conversation via get_last_messages() and splices it verbatim into the attacker's own LLM fact-extraction prompt — a direct confidentiality breach carrying whatever the victim discussed, credentials and PII included — and it writes into the same row set, where the eviction logic (DELETE FROM messages WHERE session_scope = ? AND id NOT IN (...)) can interleave or permanently evict the victim's history. Read, poison, and corrupt, all through the public Memory.add() API, no privileges beyond a normal tenant account. That is the shape CWE-863 (incorrect authorization) and CWE-200 (exposure to the wrong actor) take when the authorization decision is a string comparison against an attacker-influenced string.
Why this pattern keeps recurring
This is the same class of mistake this site keeps documenting at the boundary where agent frameworks meet storage: the framework treats a composite key as an opaque string and forgets that one of its components is attacker-controlled. AfterDeleteBench showed agent memory surviving deletion; Log-To-Leak showed post-hoc MCP logging becoming a prompt-injection channel. Mem0 adds the multi-tenant dimension: memory infrastructure borrowed query-string serialization without query-string escaping, violating the one property composite keys must have — two distinct inputs must never produce the same output. The fix, in Python SDK 2.0.18 and TypeScript SDK 3.1.6, is delimiter escaping; anything pinned below those versions in a multi-tenant deployment should be treated as sharing one memory.
What to do
- Upgrade past the fix. Python
mem0ai2.0.18+ and npmmem0ai3.1.6+. Current release lines (2.2.x / 3.3.x) already contain it — the risk sits with pinned older versions. - Audit multi-tenant identifier flow. If your application passes client- or session-supplied strings straight through as
run_idoragent_idwithout namespacing, that was the exploitable shape. Namespace identifiers per tenant server-side rather than trusting caller-supplied values. - Assume historical exposure on shared deployments. The flaw allows silent reads spliced into another tenant's extraction prompt — there is no error, no failed query, nothing to alert on. If untrusted callers shared a Mem0 backend below the fixed versions, review what lived in recent conversation buffers.
- Canonicalize composite keys everywhere. Any
key=value&key=valueserialization of mixed-trust inputs needs escaping or length-prefixing. Grep for the pattern, not just this instance.
Sources:
- NVD — CVE-2026-116575 (published 26 October 2026; CVSS 3.1 6.3, CWE-200 / CWE-863; read first-hand)
- GitHub Advisory GHSA-5324-3w58-qpp2 (published 28 October 2026; root-cause analysis, reproduction, patched versions; read first-hand)
- GitLab Advisory Database — CVE-2026-116575 mirror (affected ranges; read first-hand)