fix(mem0): scope prefetch search and result cache by user_id - #43
fix(mem0): scope prefetch search and result cache by user_id#43dizhaky wants to merge 2 commits into
Conversation
queue_prefetch() was ignoring its user_id argument and always searching
under self._user_id (set at initialize() time). In shared gateway
sessions where the cached AIAgent is reused across participants, this
caused cross-user memory leakage: one user could receive prefetched
memories originally fetched for a different user.
Fixes:
- queue_prefetch(): capture effective_user_id before spawning thread,
pass {"user_id": effective_user_id} directly instead of self._read_filters()
- Store prefetch result as (user_id, text) tuple so the consumer can
verify ownership
- prefetch(): discard the cached result when its user_id does not match
the current request — prevents stale cross-user results from leaking
Addresses Codex PR#33 P1 review comment.
🔎 Lint report:
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Empty prefetch still injects header
- Added a check for an empty cached text element in prefetch so a (user_id, "") tuple no longer returns a header-only Mem0 section.
Or push these changes by commenting:
@cursor push 3ede6df721
Preview (3ede6df721)
diff --git a/plugins/memory/mem0/__init__.py b/plugins/memory/mem0/__init__.py
--- a/plugins/memory/mem0/__init__.py
+++ b/plugins/memory/mem0/__init__.py
@@ -241,7 +241,7 @@
with self._prefetch_lock:
cached = self._prefetch_result
self._prefetch_result = None
- if not cached or cached[0] != effective_user_id:
+ if not cached or cached[0] != effective_user_id or not cached[1]:
return ""
return f"## Mem0 Memory\n{cached[1]}"You can send follow-ups to the cloud agent here.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 1c04ca5. Configure here.
| if not cached or cached[0] != effective_user_id: | ||
| return "" | ||
| return f"## Mem0 Memory\n{result}" | ||
| return f"## Mem0 Memory\n{cached[1]}" |
There was a problem hiding this comment.
Empty prefetch still injects header
Low Severity
When queue_prefetch gets Mem0 hits but every memory field is empty, the cache becomes a (user_id, "") tuple. prefetch treats any truthy tuple as valid and returns ## Mem0 Memory with no bullets. Previously an empty joined string made prefetch return nothing, so prefetch_all skipped injection.
Reviewed by Cursor Bugbot for commit 1c04ca5. Configure here.
There was a problem hiding this comment.
Fix applied in commit 3a3d3ce — added or not cached[1] to the guard in prefetch(). The empty-text tuple case now correctly returns "" rather than injecting a bare ## Mem0 Memory header with no bullets.
Reviewed and confirmed by Claude Code.
Generated by Claude Code
…njection When queue_prefetch gets results but all memory fields are empty, cached becomes (user_id, ""). prefetch was treating any truthy tuple as valid, returning "## Mem0 Memory " with no bullets. Add not cached[1] guard. Fixes Cursor Bugbot finding on PR #43.
|
Applied the Cursor Bugbot fix for Empty prefetch still injects header (commit 3a3d3ce). Analysis: The bug is valid. In Fix applied: - if not cached or cached[0] != effective_user_id:
+ if not cached or cached[0] != effective_user_id or not cached[1]:
return ""This is low-risk, correct, and matches the Bugbot autofix exactly. Generated by Claude Code |



Summary
queue_prefetch()was ignoring itsuser_idargument and searching underself._user_id(set once atinitialize()time). In shared gateway sessions where the cachedAIAgentis reused across participants, this caused cross-user memory leakage.prefetch()returned cached results without checking which user they were fetched for, so a result queued for User A could be injected into User B's next prompt.Changes
queue_prefetch(): captureeffective_user_id = user_id or self._user_idbefore spawning the background thread; pass{"user_id": effective_user_id}directly toclient.search()instead ofself._read_filters(); store result as(user_id, text)tuple.prefetch(): discard the cached result when its owneruser_iddoes not match the current caller — prevents stale cross-user results from leaking into the wrong session.__init__: initialize_prefetch_resulttoNoneinstead of""to match the new tuple-or-None shape.Test plan
thread_sessions_per_user=False), send a message as User A, then immediately as User B — confirm User B does not receive User A's prefetched memoriestests/plugins/memory/test_mem0_v2.pypassAddresses Codex PR#33 P1 review comment: Scope prefetched memory by current user.
🤖 Generated with Claude Code
https://claude.ai/code/session_01DvT8QMaMr4m9dQUAcVdypv
Generated by Claude Code
Note
Medium Risk
Fixes a privacy-sensitive cross-user leak in prompt injection; change is small and localized to prefetch only.
Overview
Fixes cross-user memory leakage when a shared gateway reuses one
Mem0MemoryProvideracross participants: background prefetch no longer ignores the per-turnuser_id.queue_prefetchnow resolveseffective_user_idfrom the call (fallbackself._user_id), searches withfilters={"user_id": effective_user_id}instead of instance-scoped_read_filters(), and stores the cache as(user_id, text).prefetchonly returns cached text when the tuple’s owner matches the current caller; otherwise it drops the cache and returns empty, so User A’s queued results cannot appear in User B’s prompt._prefetch_resultis initialized toNoneto match the tuple-or-None cache shape.Reviewed by Cursor Bugbot for commit 1c04ca5. Configure here.