Skip to content

fix: per-profile HOME isolation (#4426)#4685

Closed
kenantan32 wants to merge 2 commits into
NousResearch:mainfrom
kenantan32:fix/per-profile-home-isolation
Closed

fix: per-profile HOME isolation (#4426)#4685
kenantan32 wants to merge 2 commits into
NousResearch:mainfrom
kenantan32:fix/per-profile-home-isolation

Conversation

@kenantan32

Copy link
Copy Markdown

Summary

Adds per-profile HOME directory isolation so system tools (git, ssh, gh, npm, etc.) write configs inside the Hermes data directory instead of /root or the OS home dir. This fixes both bugs described in #4426:

  1. Docker persistence: tool configs now survive container recreates (they live in the persistent volume)
  2. Profile isolation: each profile gets its own HOME, so credentials don't bleed between profiles

Changes

hermes_cli/profiles.py

  • Added "home" to _PROFILE_DIRS — new profiles get a home/ subdirectory automatically
  • get_profile_home_dir(profile_dir) — returns and creates {profile_dir}/home/
  • apply_profile_home_isolation(profile_dir) — sets os.environ["HOME"] to the per-profile home dir. Respects explicit HOME= in .env (user config takes precedence). Silently skips commented lines.

hermes_cli/main.py

  • Calls apply_profile_home_isolation() for both default and named profiles during _apply_profile_override()
  • Default profile: uses os.environ["HERMES_HOME"] when set (Docker: /opt/data), falls back to Path.home() / ".hermes"
  • Named profiles: uses the resolved profile directory from resolve_profile_env()
  • Failures log warnings but never block startup

docker/entrypoint.sh

  • Bootstraps home/ inside the persistent volume alongside other Hermes directories

tests/hermes_cli/test_profiles.py

  • 10 new tests covering: directory creation, idempotency, _PROFILE_DIRS inclusion, env var setting, .env override respect, commented line handling, missing .env, legacy profile migration, profile isolation

Design decisions

  • If HOME= is explicitly set in the profile's .env, it takes precedence (backward compat)
  • Failures log warnings but don't block startup (except Exception: pass)
  • Existing profiles without home/ get it created on first activation (automatic migration)

Known limitation

As noted in the issue: some programs resolve home via getpwuid(getuid()) which always returns /root for the root user regardless of $HOME. Full isolation would require per-profile UIDs, which is out of scope here.

Tests

All 90 profile tests pass.

Closes #4426

Kenan Tan and others added 2 commits April 3, 2026 09:09
In Docker, HERMES_HOME=/opt/data but Path.home() returns /root.
The previous code used Path.home() / '.hermes' which would create
HOME at /root/.hermes/home/ — outside the persistent volume, defeating
the purpose of NousResearch#4426.

Now reads os.environ['HERMES_HOME'] when set, falling back to
Path.home() / '.hermes' only when HERMES_HOME is not defined.
Also removes unused 'import os as _os'.
@teknium1

Copy link
Copy Markdown
Contributor

Closed in favor of #7357, which takes a different approach: instead of setting os.environ["HOME"] globally (which breaks Path.home() across 42+ files), it injects a per-profile HOME into subprocess environments only — at the three choke points where all subprocesses are spawned. Same result (git, ssh, gh, npm write to profile-scoped dirs), zero risk to Python internals.

Thanks for the thorough implementation and tests — your PR helped define the requirements clearly.

@teknium1 teknium1 closed this Apr 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: profiles do not isolate system credentials (/root shared across profiles) and Docker setup does not persist tool configuration outside /opt/data

2 participants