SafetyCard Request a review

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

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.

Safest setup

Mem0 runs no code, and its model can only add memories inside the user, agent or run scope your code sets. It has no approval step and no handling for untrusted input, and it saves what conversations contain automatically, so the safest setup narrows the keys, ties scope to your own login and keeps the store private.

SetupHowWhat it gives you
Scoped, low-limit keysGive mem0 its own LLM key with a spending limit at the provider, and a vector-store credential limited to its collectionWhatever happens inside mem0 reaches only this app's memories and budget C1
Scope from your loginPass the user_id from your app's authenticated session, and confirm a memory belongs to that user before by-ID get, update, delete or history callsEach user reads and changes only their own memories C1
A token cap per callKeep the default max_tokens of 2000 in the LLM config, or lower itEach extraction call has a fixed ceiling on model output and cost C10
Reviewed text onlyPass infer=False to add for text your app has already reviewedOnly text you chose becomes memory, with no model extraction in the write path C3
Memory expiryPass expiration_date="<YYYY-MM-DD>" to add for memories from short-lived sessionsThose memories drop out of search and listing after that date C6
A private store locationSet the Qdrant path to a directory only the app's account can readStored memories live outside the shared system temp directory C6
Telemetry offSet MEM0_TELEMETRY=false in the environmentMemory IDs and hashed user IDs stay on your systems C8
Models you installedInstall the spaCy English model yourself at a pinned version, and point HuggingFace models at a local copy you have reviewedOnly model code you chose loads into the process that holds your keys C7

Handling data

  • The history database in ~/.mem0 keeps raw messages and old and new memory text in plaintext; store it like the conversations themselves.
  • That history table is the only record of memory changes and reset() drops it; copy it elsewhere if it needs to outlive the app.
  • Memories come back as plain text from past conversations; review a user's memories with get_all when an agent acts unexpectedly.

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

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

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

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

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

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

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

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

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

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.