We shipped a security fix for Sabine's memory system this week. The issue: episodic memory distillation—the background process that consolidates short-term memories into long-term semantic patterns—was running under service-role credentials instead of preserving user context. That meant it could theoretically read memories across user boundaries.
What Happened
Sabine's memory architecture has three layers: working (active task context), episodic (recent session history), and semantic (distilled long-term patterns). Every night, a consolidation process runs to extract insights from episodic memories and promote them to semantic storage—think of it like how your brain processes the day during sleep.
The distillation logic lived in memory_distillation.py and was called by the sleep_consolidation module. The problem: it instantiated the Supabase client with service-role permissions to handle the background job. Service-role bypasses Row Level Security (RLS). That's correct for system operations, but dangerous for reading user data.
In practice, Sabine is a single-user system (my personal assistant), so there was no actual cross-contamination. But architecturally, the code was wrong. If we ever onboard a second user—or if the pattern gets copied into a multi-tenant product—it's a vulnerability waiting to happen.
The Fix
We changed distill_episodic_to_semantic to accept an explicit user_id and pass it through to all memory queries. The consolidation job now runs with proper user context. Supabase RLS policies enforce the boundary—even if the code tries to read across users, the database won't allow it.
We also added test_sce2268_cross_user_distillation.py to regression-test the scenario: create episodic memories for two users, run distillation for user A, verify that user B's memories are untouched. The test exists to make sure this class of bug can't reappear silently.
Why It Matters
This is the kind of issue that's invisible in single-user development and catastrophic in production. Memory systems are inherently sensitive—they store context about decisions, preferences, and behavior patterns. A leak here would be a trust violation, not just a bug.
The broader lesson: background jobs need explicit user context. It's tempting to use service-role credentials for simplicity—no auth tokens to manage, no permission errors. But that convenience erases security boundaries. Passing user_id explicitly and relying on RLS is more work upfront, but it's the right architecture.
What's Next
We're auditing the rest of the memory system for similar patterns—anywhere a background process touches user data without explicit scoping. The hierarchical memory tests (test_hierarchical_memory.py) were also updated as part of this commit, which suggests there may be more consolidation paths to review.
Longer term, we're considering a pattern where all memory operations require a user context object—no naked service-role clients allowed. That would make this class of bug impossible at the type level, not just the test level. It's the kind of guardrail that pays for itself the first time it prevents a mistake.