BoundBench

Mem0

Memory layer for AI agents: an LLM extracts facts from conversations into a vector store and serves them back on search.

github.com/mem0ai/mem0 · 2026-10-04 · abb81c8

Defense-in-depth score

3.8 / 10

Minimal

Mem0's core is small: it runs no code, and its LLM can only add memories inside a scope fixed by code. The main risk is persistence. Anything said in a conversation, including injected instructions, is turned into lasting memory automatically, with no review and no untrusted label, and is served back to agents in later sessions. Multi-tenant apps should also know that get, update, delete and history by memory ID don't check which user owns the memory. Telemetry is on by default.

Key gaps (1)

  1. Optional HuggingFace/sentence-transformers models and the spaCy model mem0 downloads itself load in-process with the application's full credentials and environment. C7 · Third-party extensions

Criteria

C1 Identity & least privilege

Minimal 0.35 / 1.00

Mem0 has no identity or authorization layer of its own. Every operation runs with the single LLM key and the single vector-store credential the developer configures, and that store holds all users' memories in one collection, separated only by a user_id/agent_id/run_id payload filter that the caller supplies. Add, search and get_all require such a filter, and the LLM can't pick or widen its scope. But get, update, delete and history take only a memory ID and never check which user owns it. An app that passes memory IDs from user input therefore lets one user read or change another user's memories.

C2 Approval gates

Minimal 0.15 / 1.00

The LLM's only state-changing action is writing extracted memories to the store during add(). That happens automatically, with no human review and no hook to approve or reject a memory before it is saved. The extraction pipeline is add-only: it never updates or deletes existing memories. Each write gets a returned ID and a history row, so a bad memory can be found and deleted afterwards. Mem0 itself takes no external actions such as sending messages.

C3 Tool & action scoping

Moderate 0.57 / 1.00

The model gets no general tools. Its output is parsed as JSON, and only memory text and an attribution field are kept. Code then inserts that text as new memories under a scope that code, not the model, fixed. Query parameters are validated (threshold and top_k bounds, entity-ID checks), and vector-store queries use parameterized SQL. The model can't choose an operation, a target scope or a destination. However, nothing limits how many memories one call can create, and the developer-facing by-ID methods skip scope validation.

C4 Code-execution isolation

N/A · full credit 1.00 / 1.00

The core SDK never runs model-generated or workspace-supplied text as code. It has no shell, Python execution, eval, or template rendering, and the model's output is only parsed as JSON. The only subprocess in reach is spaCy's fixed model download, which runs only when the optional NLP extra is installed. It is scored under third-party extensions.

C5 Untrusted input blast radius

Minimal 0.05 / 1.00

Conversation content of any origin goes into the extraction prompt as plain markdown sections, with no injection handling. The model's output is saved as durable memory without being marked as untrusted. Within mem0 itself, a hijacked extraction can only add memories to the current user's scope: it can't read other users' data, delete anything, or send data out. But mem0's own OpenAI-compatible proxy pastes stored memories into the user turn as plain text and forwards the caller's tools to the model. A one-time injection can therefore come back in later sessions and steer a host agent's tool calls.

C6 Memory, context & configuration integrity

Minimal 0.30 / 1.00

Persistent memory is mem0's core purpose, and the model writes to it automatically. Facts extracted from any conversation are stored with no validation, review or expiry by default (expiration is opt-in). Each write is recorded in a SQLite history table. Memories are separated by a required user, agent or run scope that the model can't change. However, the default local store sits at /tmp/qdrant, a shared system directory, and nothing tags stored memories as untrusted when they are served back. A poisoned memory persists across the user's sessions until someone deletes it.

C7 Third-party extensions

Minimal 0.13 / 1.00

The default configuration (OpenAI and local Qdrant) loads no third-party code at runtime. When the optional NLP extra is installed, mem0 downloads and installs the en_core_web_sm spaCy package by itself on first use, with no prompt. The optional HuggingFace embedder and reranker load whatever model name is configured, with no revision pinning or integrity check. All of these load inside the application's process, with its keys and environment.

C8 Secrets & sensitive-data protection

Minimal 0.25 / 1.00

API keys come from configuration or environment variables and are never put into prompts. A redaction helper blanks secret-looking fields when configs are cloned, and the server redacts config output. Telemetry to PostHog is on by default. It sends no memory content, but it does send memory IDs, unsalted MD5 hashes of user IDs, system details, and for the hosted client the account email. Raw conversation messages are stored in plaintext in ~/.mem0/history.db, and some warnings log whole message objects.

C9 Audit & traceability

Moderate 0.50 / 1.00

Every memory add, update and delete is written to a SQLite history table with old and new text, the event type, timestamps, and actor and role fields (not filled in on the batch-add path). It is on by default and stored in ~/.mem0, outside any workspace. The record does not note which user scope a change belongs to. reset() drops the whole table, the entity store has no history, and if writing a history row fails, mem0 logs an error and keeps the memory anyway.

C10 Limits & kill switch

Moderate 0.50 / 1.00

Mem0 has no agent loop. Each add() makes one bounded extraction call (10 existing memories, the last 10 messages, max_tokens 2000 by default), and the model can't raise these limits. There is no wall-clock limit beyond the provider SDK's timeouts, no cost cap, and no cancel. The proxy starts a new background thread for every request with no limit on how many run at once.