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:
nesquena-hermes
2026-04-21 09:16:51 -07:00
committed by GitHub
parent d527629281
commit cbb4ba3f28
7 changed files with 232 additions and 13 deletions

View File

@@ -18,7 +18,7 @@ async function newSession(flash){
// otherwise inherit from the current session (or let server pick the default)
const inheritWs=S._profileDefaultWorkspace||(S.session?S.session.workspace:null);
S._profileDefaultWorkspace=null; // consume — only applies to the first new session after switch
const data=await api('/api/session/new',{method:'POST',body:JSON.stringify({model:$('modelSelect').value,workspace:inheritWs})});
const data=await api('/api/session/new',{method:'POST',body:JSON.stringify({model:$('modelSelect').value,workspace:inheritWs,profile:S.activeProfile||'default'})});
S.session=data.session;S.messages=data.session.messages||[];
S.lastUsage={...(data.session.last_usage||{})};
if(flash)S.session._flash=true;