The short version: On July 13, 2026, mem0 shipped Python SDK v2.0.12, and the headline line in the changelog is not a feature — it's a fix: "Fix SQL and Cypher injection vulnerabilities in the PGVector, Azure MySQL, and Neptune providers" (#4878). The same release added filter validation for Elasticsearch; the previous one, v2.0.11 (July 1), escaped Neptune's openCypher filters and validated OpenSearch. Five real database backends, two releases, one class of bug. If you run mem0, upgrade. But the reason this is a Wire story and not a one-line release note is what it says about where the hole was.

The injection didn't come through the prompt#

For two years the entire agent-security conversation has pointed at the prompt. Jailbreaks, prompt injection, tool-argument poisoning, injection that escalates to remote code execution through a permissive allowlist — all of it treats the model's input as the attack surface, because that's the new and interesting part. The industry built classifiers and guardrails to watch that door.

Mem0's patch was for a different door entirely, and it's an old one. The tainted string didn't ride in through the LLM prompt. It rode in through a metadata filter — the filters={...} you hand to search() and add() — which the storage providers compiled straight into raw SQL (PGVector, Azure MySQL) or Cypher / openCypher (Neptune). A filter value that got string-joined into that query is injectable in precisely the way a 1998 web form was injectable. Nothing about "AI" changed the mechanics; the AI stack just gave the bug a new house to live in.

Everyone is watching the prompt for the clever new attack. The memory layer was quietly compiling untrusted strings into your database.

Why this is worse in an agent, not better#

You might think: fine, but I control my filters. In a classic app you often do. In an agent, you frequently don't.

Agent memory filters are routinely built from values that are, at best, user-controlled and, at worst, model-controlled. The user_id you scope a search to came from a request. The metadata tag you filter on — a project name, a category, a customer identifier — was often extracted by the model itself from a conversation before being written back and later queried. So the untrusted text you spent all that effort keeping out of your SQL walks right back in as a "memory filter," through a component nobody on the team threat-models as a database, because the docs call it memory.

That's the non-obvious part worth carrying out of this: the retrieval layer is a query surface, and an agent is a machine for turning untrusted language into structured parameters. Those two facts intersect exactly at the filter you pass to search().

What to actually do this week#

1. Upgrade. Python mem0 to v2.0.12, Node to v3.1.0. This closes the specific holes in PGVector, Azure MySQL, Neptune, OpenSearch, and Elasticsearch. It's the cheap, obvious step. (While you're bumping: v2.0.12 also switches Memory.update() to a text= argument — the old data= is deprecated but still works — and stops crashing when you pass a non-string user_id, both noted in the same release. We covered the engine those APIs sit on in Inside Mem0 2.x.)

2. Don't stop at mem0. The fix landed in mem0, but the pattern is not mem0's alone. Any place your code turns a caller-supplied value into a query, filter, namespace, or tag has this surface — your own metadata store, a hand-rolled vector filter, a graph query you assemble as a string. Grep for it. If a filter value is concatenated rather than parameterized, escaped, or validated against an allowlist, treat it as the same bug wearing different branding.

3. Scope memory so a filter can't reach across tenants. Partition memory per subject — a namespace or container per user — so even a malformed or hostile filter can't pull another user's facts. That's the same isolation that makes deletion and right-to-be-forgotten tractable, and it doubly pays off here: a filter injection inside a one-user partition is a much smaller fire than one against a shared store. If you're choosing a backend, this is another column in the mem0 vs Zep vs Letta and Cognee vs Graphiti vs Mem0 comparison — not just recall and price, but how the layer builds its queries.

The memory category spent this year competing on how much an agent can remember and, lately, on whether it can prove what it forgot. July 13 was a reminder of the boring requirement underneath both: the thing storing all that memory is a database, it takes queries built from untrusted input, and it has to be defended like one. Mem0 just did. The question the patch hands you is whether the filter-building in your code — the parts mem0 never touches — has been.