fix: isolate profile .env secrets on switch (#351)

* fix: isolate profile .env secrets on switch

* fix: move direct os.environ set after _reload_dotenv to survive profile isolation

The profile env isolation in _reload_dotenv now clears previously tracked
env keys before re-reading .env. When apply_onboarding_setup set
os.environ BEFORE _reload_dotenv, the key was immediately cleared.
Move the belt-and-braces os.environ set to AFTER _reload_dotenv so
the API key survives regardless of profile tracking state.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hinotobi
2026-04-13 15:51:55 +08:00
committed by GitHub
parent 1fee123ac8
commit 88dc8bbe26
3 changed files with 91 additions and 5 deletions

View File

@@ -26,6 +26,7 @@ _CLONE_CONFIG_FILES = ['config.yaml', '.env', 'SOUL.md']
# ── Module state ────────────────────────────────────────────────────────────
_active_profile = 'default'
_profile_lock = threading.Lock()
_loaded_profile_env_keys: set[str] = set()
def _resolve_base_hermes_home() -> Path:
"""Return the BASE ~/.hermes directory — the root that contains profiles/.
@@ -120,11 +121,24 @@ def _set_hermes_home(home: Path):
def _reload_dotenv(home: Path):
"""Load .env from the profile dir into os.environ (additive)."""
"""Load .env from the profile dir into os.environ with profile isolation.
Clears env vars that were loaded from the previously active profile before
applying the current profile's .env. This prevents API keys and other
profile-scoped secrets from leaking across profile switches.
"""
global _loaded_profile_env_keys
# Remove keys loaded from the previous profile first.
for key in list(_loaded_profile_env_keys):
os.environ.pop(key, None)
_loaded_profile_env_keys = set()
env_path = home / '.env'
if not env_path.exists():
return
try:
loaded_keys: set[str] = set()
for line in env_path.read_text().splitlines():
line = line.strip()
if line and not line.startswith('#') and '=' in line:
@@ -133,8 +147,10 @@ def _reload_dotenv(home: Path):
v = v.strip().strip('"').strip("'")
if k and v:
os.environ[k] = v
loaded_keys.add(k)
_loaded_profile_env_keys = loaded_keys
except Exception:
pass
_loaded_profile_env_keys = set()
def init_profile_state() -> None: