fix(gateway): enforce chat_id isolation for all DM sessions

This commit is contained in:
heyyyimmax 2026-03-12 16:21:49 +01:00 committed by teknium1
parent 779f8df6a6
commit 34e120bcbb
2 changed files with 21 additions and 14 deletions

View file

@ -321,25 +321,32 @@ def build_session_key(source: SessionSource) -> str:
This is the single source of truth for session key construction. This is the single source of truth for session key construction.
DM rules: DM rules:
- WhatsApp DMs include chat_id (multi-user support). - DMs include chat_id when present, so each private conversation is isolated.
- Other DMs include thread_id when present (e.g. Slack threaded DMs), - thread_id further differentiates threaded DMs within the same DM chat.
so each DM thread gets its own session while top-level DMs share one. - Without chat_id, thread_id is used as a best-effort fallback.
- Without thread_id or chat_id, all DMs share a single session. - Without thread_id or chat_id, DMs share a single session.
Group/channel rules: Group/channel rules:
- thread_id differentiates threads within a channel. - chat_id identifies the parent group/channel.
- Without thread_id, all messages in a channel share one session. - thread_id differentiates threads within that parent chat.
- Without identifiers, messages fall back to one session per platform/chat_type.
""" """
platform = source.platform.value platform = source.platform.value
if source.chat_type == "dm": if source.chat_type == "dm":
if source.chat_id:
if source.thread_id:
return f"agent:main:{platform}:dm:{source.chat_id}:{source.thread_id}"
return f"agent:main:{platform}:dm:{source.chat_id}"
if source.thread_id: if source.thread_id:
return f"agent:main:{platform}:dm:{source.thread_id}" return f"agent:main:{platform}:dm:{source.thread_id}"
if platform == "whatsapp" and source.chat_id:
return f"agent:main:{platform}:dm:{source.chat_id}"
return f"agent:main:{platform}:dm" return f"agent:main:{platform}:dm"
if source.chat_id:
if source.thread_id:
return f"agent:main:{platform}:{source.chat_type}:{source.chat_id}:{source.thread_id}"
return f"agent:main:{platform}:{source.chat_type}:{source.chat_id}"
if source.thread_id: if source.thread_id:
return f"agent:main:{platform}:{source.chat_type}:{source.chat_id}:{source.thread_id}" return f"agent:main:{platform}:{source.chat_type}:{source.thread_id}"
return f"agent:main:{platform}:{source.chat_type}:{source.chat_id}" return f"agent:main:{platform}:{source.chat_type}"
class SessionStore: class SessionStore:

View file

@ -338,7 +338,7 @@ class TestSessionStoreRewriteTranscript:
class TestWhatsAppDMSessionKeyConsistency: class TestWhatsAppDMSessionKeyConsistency:
"""Regression: all session-key construction must go through build_session_key """Regression: all session-key construction must go through build_session_key
so WhatsApp DMs include chat_id while other DMs do not.""" so DMs are isolated by chat_id across platforms."""
@pytest.fixture() @pytest.fixture()
def store(self, tmp_path): def store(self, tmp_path):
@ -369,15 +369,15 @@ class TestWhatsAppDMSessionKeyConsistency:
) )
assert store._generate_session_key(source) == build_session_key(source) assert store._generate_session_key(source) == build_session_key(source)
def test_telegram_dm_omits_chat_id(self): def test_telegram_dm_includes_chat_id(self):
"""Non-WhatsApp DMs should still omit chat_id (single owner DM).""" """Non-WhatsApp DMs should also include chat_id to separate users."""
source = SessionSource( source = SessionSource(
platform=Platform.TELEGRAM, platform=Platform.TELEGRAM,
chat_id="99", chat_id="99",
chat_type="dm", chat_type="dm",
) )
key = build_session_key(source) key = build_session_key(source)
assert key == "agent:main:telegram:dm" assert key == "agent:main:telegram:dm:99"
def test_discord_group_includes_chat_id(self): def test_discord_group_includes_chat_id(self):
"""Group/channel keys include chat_type and chat_id.""" """Group/channel keys include chat_type and chat_id."""