fix(profiles): profile isolation — new_session uses per-request profile, not process global (#800)
Fixes the multi-client profile isolation bug (#798). - get_hermes_home_for_profile(): pure path resolver, validates name against _PROFILE_ID_RE (rejects path traversal), never mutates os.environ or globals - new_session() accepts explicit profile= param from POST body (S.activeProfile), short-circuits the process-level _active_profile global - streaming handler resolves HERMES_HOME from s.profile instead of the global - sessions.js sends profile: S.activeProfile in every new-session POST 10 tests in tests/test_issue798.py including concurrency and traversal coverage. Co-authored-by: nesquena <nesquena@users.noreply.github.com>
This commit is contained in:
@@ -100,6 +100,26 @@ def get_active_hermes_home() -> Path:
|
||||
return _DEFAULT_HERMES_HOME
|
||||
|
||||
|
||||
|
||||
def get_hermes_home_for_profile(name: str) -> Path:
|
||||
"""Return the HERMES_HOME Path for *name* without mutating any process state.
|
||||
|
||||
Safe to call from per-request context (streaming, session creation) because
|
||||
it reads only the filesystem — it never touches os.environ, module-level
|
||||
cached paths, or the process-level _active_profile global.
|
||||
|
||||
Falls back to _DEFAULT_HERMES_HOME (same as 'default') when *name* is None,
|
||||
empty, 'default', or does not match the profile-name format (rejects path
|
||||
traversal such as '../../etc').
|
||||
"""
|
||||
if not name or name == 'default' or not _PROFILE_ID_RE.match(name):
|
||||
return _DEFAULT_HERMES_HOME
|
||||
profile_dir = _DEFAULT_HERMES_HOME / 'profiles' / name
|
||||
if profile_dir.is_dir():
|
||||
return profile_dir
|
||||
return _DEFAULT_HERMES_HOME
|
||||
|
||||
|
||||
def _set_hermes_home(home: Path):
|
||||
"""Set HERMES_HOME env var and monkey-patch cached module-level paths."""
|
||||
os.environ['HERMES_HOME'] = str(home)
|
||||
|
||||
Reference in New Issue
Block a user