diff --git a/api/profiles.py b/api/profiles.py index c18cfe9f..b0462108 100644 --- a/api/profiles.py +++ b/api/profiles.py @@ -376,10 +376,17 @@ def _resolve_profile_home_for_name(name: str) -> Path: names fall back to the base home so traversal-shaped cookie values cannot influence filesystem paths. """ - # In isolated mode, the pinned profile name, including a literal - # "default", must resolve to the configured startup HERMES_HOME. - if _is_isolated_profile_mode() and name == _isolated_profile_name(): - return Path(_INITIAL_HERMES_HOME).expanduser() + # In isolated mode, every logical profile lookup clamps to the configured + # startup HERMES_HOME so callers cannot resolve a foreign profile path. + if _is_isolated_profile_mode(): + isolated_name = _isolated_profile_name() + isolated_home = Path(_INITIAL_HERMES_HOME).expanduser() + if name and not _profiles_match(name, isolated_name): + logger.warning( + "Ignoring profile lookup %r in isolated profile mode; using pinned profile %r", + name, isolated_name, + ) + return isolated_home if not name or _is_root_profile(name): return _DEFAULT_HERMES_HOME if not _PROFILE_ID_RE.fullmatch(name): diff --git a/tests/test_issue2698_isolated_hermes_home.py b/tests/test_issue2698_isolated_hermes_home.py index 8b8e3e47..94750b89 100644 --- a/tests/test_issue2698_isolated_hermes_home.py +++ b/tests/test_issue2698_isolated_hermes_home.py @@ -291,6 +291,19 @@ class TestIsolatedRuntimePinning: assert get_hermes_home_for_profile("default") == isolated_default assert get_hermes_home_for_profile("default") != temp_hermes_home + def test_explicit_foreign_profile_resolution_stays_on_isolated_home(self, temp_single_profile, monkeypatch): + """Explicit profile lookups must not escape the isolated startup home.""" + from api.profiles import get_hermes_home_for_profile, _resolve_profile_home_for_name + + base_home = temp_single_profile.parent.parent + monkeypatch.setenv("HERMES_HOME", str(temp_single_profile)) + monkeypatch.setenv("HERMES_BASE_HOME", "") + monkeypatch.setattr(_profiles_mod, "_DEFAULT_HERMES_HOME", base_home) + monkeypatch.setattr(_profiles_mod, "_INITIAL_HERMES_HOME", str(temp_single_profile)) + + assert _resolve_profile_home_for_name("other_profile") == temp_single_profile + assert get_hermes_home_for_profile("other_profile") == temp_single_profile + def test_list_profiles_prefers_matching_isolated_default_home(self, temp_hermes_home, monkeypatch): """The isolated profiles/default row must not collapse to the base-home row.""" isolated_default = temp_hermes_home / "profiles" / "default"