Root cause
Authentication & access control
feat: multi-profile support -- create, switch, delete profiles from web UI (Issue #28)
How AI contributed: New attack surfacePublished Apr 3, 2026 in
nesquena/hermes-webuiAll 6 findings in this repository
Hermes WebUI before version 0.51.269 contains a profile isolation bypass vulnerability that allows authenticated users to access data belonging to other profiles by querying the session search endpoint without active-profile filtering. Attackers can send requests to the sessions search handler to retrieve session titles and transcript message content from profiles other than their own active profile.
Hermes WebUI’s AI-written session search handler omitted active-profile filtering, letting any authenticated user retrieve other profiles’ session titles and transcripts.
Root cause
feat: multi-profile support -- create, switch, delete profiles from web UI (Issue #28)
Fix
fix: scope session search to active profile
Case facts
diff --git a/api/routes.py b/api/routes.pynew file mode 100644index 0000000..190bcd7b64680ceeedee4171f9f275117421bba0--- /dev/null+++ b/api/routes.py@@ -0,0 +369,27 @@+def _handle_sessions_search(handler, parsed):+ qs = parse_qs(parsed.query)+ q = qs.get('q', [''])[0].lower().strip()+ content_search = qs.get('content', ['1'])[0] == '1'+ depth = int(qs.get('depth', ['5'])[0])+ if not q: return j(handler, {'sessions': all_sessions()})+ results = []+ for s in all_sessions():+ title_match = q in (s.get('title') or '').lower()+ if title_match:+ results.append(dict(s, match_type='title'))+ continue+ if content_search:+ try:+ sess = get_session(s['session_id'])+ msgs = sess.messages[:depth] if depth else sess.messages+ for m in msgs:+ c = m.get('content') or ''+ if isinstance(c, list):+ c = ' '.join(p.get('text', '') for p in c+ if isinstance(p, dict) and p.get('type') == 'text')+ if q in str(c).lower():+ results.append(dict(s, match_type='content'))+ break+ except (KeyError, Exception):+ pass+ return j(handler, {'sessions': results, 'query': q, 'count': len(results)})diff --git a/api/routes.py b/api/routes.pyindex 43f69eb6..d5c5be6a 100644--- a/api/routes.py+++ b/api/routes.py@@ -8291,0 +8292,9 @@ def _handle_sessions_search(handler, parsed):+ from api.profiles import get_active_profile_name+ active_profile = get_active_profile_name()+ all_profiles = _all_profiles_query_flag(parsed)+ sessions = all_sessions()+ if not all_profiles:+ sessions = [+ s for s in sessions+ if _profiles_match(s.get("profile"), active_profile)+ ]@@ -8303 +8312 @@ def _handle_sessions_search(handler, parsed):- for s in all_sessions():+ for s in sessions:@@ -8308 +8317,5 @@ def _handle_sessions_search(handler, parsed):- return j(handler, {"sessions": safe_sessions})+ return j(handler, {+ "sessions": safe_sessions,+ "all_profiles": all_profiles,+ "active_profile": active_profile,+ })@@ -8310 +8323 @@ def _handle_sessions_search(handler, parsed):- for s in all_sessions():+ for s in sessions:@@ -8335 +8348,7 @@ def _handle_sessions_search(handler, parsed):- return j(handler, {"sessions": results, "query": q, "count": len(results)})+ return j(handler, {+ "sessions": results,+ "query": q,+ "count": len(results),+ "all_profiles": all_profiles,+ "active_profile": active_profile,+ })AI-assisted change 0b388acb56c7c5214658f0dee9862a833775c008449b3b5993c951f91b868087 · Fix e29a8171081401721719f3345ca122f010a933d520f8e565a135e8477931c138