diff --git a/CHANGELOG.md b/CHANGELOG.md index f313366..32f2acf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Hermes Web UI -- Changelog +## [v0.50.36] fix: workspace list cleaner — allow own-profile paths, remove brittle string filter + +Two bugs in `_clean_workspace_list()` caused workspace additions to silently disappear on the next `load_workspaces()` call, breaking `test_workspace_add_no_duplicate` and `test_workspace_rename` (and potentially causing real-world workspace list corruption): + +**Bug 1 — Brittle string filter removed:** `if 'test-workspace' in path or 'webui-mvp-test' in path: continue` dropped any workspace path containing those substrings. In the test server, `TEST_WORKSPACE` is `~/.hermes/profiles/webui/webui-mvp-test/test-workspace`, so every workspace added during tests was silently discarded on the next `load_workspaces()` call. The `p.is_dir()` check already handles genuinely non-existent paths — the string filter was redundant and harmful. + +**Bug 2 — Cross-profile filter was too broad:** `if p is under ~/.hermes/profiles/: skip` was designed to block cross-profile workspace leakage, but it also removed paths under the *current* profile's own directory (e.g. `~/.hermes/profiles/webui/...`). Fixed: now only skips paths under `profiles/` that are NOT under the current profile's own `hermes_home`. + +- `api/workspace.py`: remove string-match filter; fix cross-profile check to allow own-profile paths +- All 1055 tests now pass (was 1053 pass + 2 fail) + ## [v0.50.35] fix: workspace trust boundary — cross-platform, multi-workspace support v0.50.34's workspace trust check was too restrictive: it required all workspaces to be under `DEFAULT_WORKSPACE` (/home/hermes/workspace), which blocked every profile-specific workspace (~/CodePath, ~/hermes-webui-public, ~/WebUI, ~/Camanji, etc.) and prevented switching between workspaces at all. diff --git a/api/workspace.py b/api/workspace.py index ab41e3f..7096882 100644 --- a/api/workspace.py +++ b/api/workspace.py @@ -92,7 +92,6 @@ def _profile_default_workspace() -> str: def _clean_workspace_list(workspaces: list) -> list: """Sanitize a workspace list: - Remove entries whose paths no longer exist on disk. - - Remove entries that look like test artifacts (webui-mvp-test, test-workspace). - Remove entries whose paths live inside another profile's directory (e.g. ~/.hermes/profiles/X/... should not appear on a different profile). - Rename any entry whose name is literally 'default' to 'Home' (avoids @@ -105,18 +104,24 @@ def _clean_workspace_list(workspaces: list) -> list: path = w.get('path', '') name = w.get('name', '') p = Path(path).resolve() if path else Path('/') - # Skip test artifacts - if 'test-workspace' in path or 'webui-mvp-test' in path: - continue # Skip paths that no longer exist if not p.is_dir(): continue - # Skip paths inside a named profile's directory (cross-profile leak) + # Skip paths inside a DIFFERENT profile's directory (cross-profile leak). + # Allow paths inside the CURRENT profile's own directory (e.g. test workspaces + # created under ~/.hermes/profiles/webui/webui-mvp-test/). try: p.relative_to(hermes_profiles) - continue # it IS under profiles/ — remove it + # p is under ~/.hermes/profiles/ — only skip if it's under a DIFFERENT profile + try: + from api.profiles import get_active_hermes_home + own_profile_dir = get_active_hermes_home().resolve() + p.relative_to(own_profile_dir) + # p is under our own profile dir — keep it + except (ValueError, Exception): + continue # under profiles/ but not our own — cross-profile leak, skip except ValueError: - pass + pass # not under profiles/ at all — keep it # Rename confusing 'default' label to 'Home' if name.lower() == 'default': name = 'Home' diff --git a/static/index.html b/static/index.html index 8bdba96..6cc3d41 100644 --- a/static/index.html +++ b/static/index.html @@ -535,7 +535,7 @@
System
Instance version and access controls.
- v0.50.35 + v0.50.36