fix(gateway): avoid cross-profile session recovery (#59325)

Co-authored-by: yoma <yingwaizhiying@gmail.com>
This commit is contained in:
Teknium 2026-07-07 01:25:39 -07:00 committed by GitHub
parent d297568299
commit f1fde49e45
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 115 additions and 0 deletions

View file

@ -1259,6 +1259,45 @@ class SessionStore:
except Exception:
return None
@staticmethod
def _profile_from_session_key(session_key: Optional[str]) -> Optional[str]:
"""Extract the profile namespace encoded in a gateway session key."""
if not session_key:
return None
parts = str(session_key).split(":")
if len(parts) < 2 or parts[0] != "agent":
return None
namespace = parts[1] or "main"
return "default" if namespace == "main" else namespace
@staticmethod
def _active_profile_name() -> str:
try:
from hermes_cli.profiles import get_active_profile_name
return get_active_profile_name() or "default"
except Exception:
return "default"
def _recovered_row_allowed_for_active_profile(
self,
*,
requested_session_key: str,
recovered: Dict[str, Any],
) -> bool:
"""Prevent non-multiplexed gateways from reviving another profile's row."""
if getattr(self.config, "multiplex_profiles", False):
return True
recovered_key = str(recovered.get("session_key") or "")
if not recovered_key or recovered_key == requested_session_key:
return True
recovered_profile = self._profile_from_session_key(recovered_key)
if recovered_profile is None:
return True
return recovered_profile == self._active_profile_name()
def _generate_session_key(self, source: SessionSource) -> str:
"""Generate a session key from a source."""
return build_session_key(
@ -1319,6 +1358,18 @@ class SessionStore:
return None
if not recovered:
return None
if not self._recovered_row_allowed_for_active_profile(
requested_session_key=session_key,
recovered=recovered,
):
logger.warning(
"Gateway session DB recovery ignored %s for %s because "
"multiplex_profiles is disabled and the row belongs to a "
"different profile",
recovered.get("session_key"),
session_key,
)
return None
try:
self._db.reopen_session(str(recovered["id"]))
except Exception as exc:

View file

@ -9,6 +9,7 @@ Covers the three Phase 0 deliverables:
on.
"""
import pytest
from datetime import datetime
from unittest.mock import patch
import yaml
@ -201,3 +202,66 @@ class TestSessionStoreProfileResolution:
with patch("hermes_cli.profiles.get_active_profile_name", return_value="default"):
assert store._generate_session_key(s) == "agent:main:telegram:dm:99"
class _RecoveringDB:
def __init__(self, row):
self.row = row
self.reopened = []
def find_latest_gateway_session_for_peer(self, **_kwargs):
return self.row
def reopen_session(self, session_id):
self.reopened.append(session_id)
class TestSessionStoreUnmultiplexedRecovery:
"""Turning multiplexing off must not recover another profile's session."""
def _store_with_row(self, tmp_path, row, **cfg_kw):
config = GatewayConfig(**cfg_kw)
with patch("gateway.session.SessionStore._ensure_loaded"):
store = SessionStore(sessions_dir=tmp_path, config=config)
store._db = _RecoveringDB(row)
store._loaded = True
return store
def test_flag_off_rejects_other_profile_peer_fallback(self, tmp_path):
row = {
"id": "sess-coder",
"started_at": 1700000000,
"session_key": "agent:coder:telegram:dm:99",
}
store = self._store_with_row(tmp_path, row)
source = _src(chat_id="99", chat_type="dm")
with patch("hermes_cli.profiles.get_active_profile_name", return_value="default"):
recovered = store._recover_session_from_db(
session_key="agent:main:telegram:dm:99",
source=source,
now=datetime.fromtimestamp(1700000001),
)
assert recovered is None
assert store._db.reopened == []
def test_flag_off_allows_active_profile_peer_fallback(self, tmp_path):
row = {
"id": "sess-coder",
"started_at": 1700000000,
"session_key": "agent:coder:telegram:dm:99",
}
store = self._store_with_row(tmp_path, row)
source = _src(chat_id="99", chat_type="dm")
with patch("hermes_cli.profiles.get_active_profile_name", return_value="coder"):
recovered = store._recover_session_from_db(
session_key="agent:main:telegram:dm:99",
source=source,
now=datetime.fromtimestamp(1700000001),
)
assert recovered is not None
assert recovered.session_id == "sess-coder"
assert recovered.session_key == "agent:main:telegram:dm:99"
assert store._db.reopened == ["sess-coder"]