Skip to content

fix: per-profile subprocess HOME isolation (#4426)#7357

Merged
teknium1 merged 1 commit into
mainfrom
hermes/hermes-f1c0a201
Apr 10, 2026
Merged

fix: per-profile subprocess HOME isolation (#4426)#7357
teknium1 merged 1 commit into
mainfrom
hermes/hermes-f1c0a201

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Isolates system tool configs (git, ssh, gh, npm) per profile by injecting a per-profile HOME into subprocess environments only. The Python process's own os.environ["HOME"] and Path.home() are never modified.

Closes #4426

The problem

Two related bugs share a root cause:

  1. Docker: tool configs written to /root/ don't persist when the container is recreated — only /opt/data (the persistent volume) survives.
  2. Profiles: all profiles share /root/'s git identity, SSH keys, gh tokens, etc.

Why previous approaches were rejected

PR #4437 and PR #4685 both set os.environ["HOME"] globally in the Python process. This breaks Path.home() which is used by 42 files — profile infrastructure (_get_profiles_root(), _get_default_hermes_home(), _get_wrapper_dir()), systemd/launchd paths, and every Python library that calls os.path.expanduser().

This approach: subprocess-only injection

Every subprocess the agent spawns goes through one of three choke points that already build custom env dicts. We inject HOME={HERMES_HOME}/home/ at these three points:

Choke point File Covers
_make_run_env() tools/environments/local.py Foreground terminal commands
_sanitize_subprocess_env() tools/environments/local.py Background processes (PTY + non-PTY)
child_env construction tools/code_execution_tool.py execute_code sandbox

Single source of truth: hermes_constants.get_subprocess_home()

Activation

Directory-based — zero config needed:

  • {HERMES_HOME}/home/ exists → subprocesses get it as HOME
  • Doesn't exist → behavior unchanged

Who creates the directory:

  • Docker: entrypoint.sh bootstraps it inside the persistent volume
  • Named profiles: added "home" to _PROFILE_DIRS in profiles.py
  • Default non-Docker installs: not created → zero behavior change

What this preserves

  • Path.home() untouched → profile infrastructure, systemd paths, wrapper dirs all work
  • os.environ["HOME"] untouched → no impact on Python process internals
  • Shell initialization unaffected — bash -c (non-login, used for terminal commands) doesn't source profile files

Changes

File Change
hermes_constants.py New get_subprocess_home() — returns {HERMES_HOME}/home/ if it exists
tools/environments/local.py Inject HOME in _make_run_env() and _sanitize_subprocess_env()
tools/code_execution_tool.py Inject HOME in child_env construction
hermes_cli/profiles.py Add "home" to _PROFILE_DIRS
docker/entrypoint.sh Add home to bootstrapped directories
tests/test_subprocess_home_isolation.py 13 tests covering all paths

Test plan

  • python3 -m pytest tests/test_subprocess_home_isolation.py — 13/13 ✓
  • python3 -m pytest tests/tools/test_local_env_blocklist.py tests/hermes_cli/test_profiles.py tests/tools/test_env_passthrough.py — 121/121 ✓
  • python3 -m pytest tests/tools/test_code_execution.py — 63/63 ✓

Isolate system tool configs (git, ssh, gh, npm) per profile by injecting
a per-profile HOME into subprocess environments only.  The Python
process's own os.environ['HOME'] and Path.home() are never modified,
preserving all existing profile infrastructure.

Activation is directory-based: when {HERMES_HOME}/home/ exists on disk,
subprocesses see it as HOME.  The directory is created automatically for:
- Docker: entrypoint.sh bootstraps it inside the persistent volume
- Named profiles: added to _PROFILE_DIRS in profiles.py

Injection points (all three subprocess env builders):
- tools/environments/local.py _make_run_env() — foreground terminal
- tools/environments/local.py _sanitize_subprocess_env() — background procs
- tools/code_execution_tool.py child_env — execute_code sandbox

Single source of truth: hermes_constants.get_subprocess_home()

Closes #4426
@teknium1 teknium1 merged commit 4fb42d0 into main Apr 10, 2026
5 of 6 checks passed
Tommyeds pushed a commit to Tommyeds/hermes-agent that referenced this pull request Apr 12, 2026
…esearch#7357)

Isolate system tool configs (git, ssh, gh, npm) per profile by injecting
a per-profile HOME into subprocess environments only.  The Python
process's own os.environ['HOME'] and Path.home() are never modified,
preserving all existing profile infrastructure.

Activation is directory-based: when {HERMES_HOME}/home/ exists on disk,
subprocesses see it as HOME.  The directory is created automatically for:
- Docker: entrypoint.sh bootstraps it inside the persistent volume
- Named profiles: added to _PROFILE_DIRS in profiles.py

Injection points (all three subprocess env builders):
- tools/environments/local.py _make_run_env() — foreground terminal
- tools/environments/local.py _sanitize_subprocess_env() — background procs
- tools/code_execution_tool.py child_env — execute_code sandbox

Single source of truth: hermes_constants.get_subprocess_home()

Closes NousResearch#4426
ulasbilgen pushed a commit to ulasbilgen/hermes-adhd-agent that referenced this pull request May 1, 2026
…esearch#7357)

Isolate system tool configs (git, ssh, gh, npm) per profile by injecting
a per-profile HOME into subprocess environments only.  The Python
process's own os.environ['HOME'] and Path.home() are never modified,
preserving all existing profile infrastructure.

Activation is directory-based: when {HERMES_HOME}/home/ exists on disk,
subprocesses see it as HOME.  The directory is created automatically for:
- Docker: entrypoint.sh bootstraps it inside the persistent volume
- Named profiles: added to _PROFILE_DIRS in profiles.py

Injection points (all three subprocess env builders):
- tools/environments/local.py _make_run_env() — foreground terminal
- tools/environments/local.py _sanitize_subprocess_env() — background procs
- tools/code_execution_tool.py child_env — execute_code sandbox

Single source of truth: hermes_constants.get_subprocess_home()

Closes NousResearch#4426
02356abc pushed a commit to 02356abc/hermes-agent that referenced this pull request May 14, 2026
…esearch#7357)

Isolate system tool configs (git, ssh, gh, npm) per profile by injecting
a per-profile HOME into subprocess environments only.  The Python
process's own os.environ['HOME'] and Path.home() are never modified,
preserving all existing profile infrastructure.

Activation is directory-based: when {HERMES_HOME}/home/ exists on disk,
subprocesses see it as HOME.  The directory is created automatically for:
- Docker: entrypoint.sh bootstraps it inside the persistent volume
- Named profiles: added to _PROFILE_DIRS in profiles.py

Injection points (all three subprocess env builders):
- tools/environments/local.py _make_run_env() — foreground terminal
- tools/environments/local.py _sanitize_subprocess_env() — background procs
- tools/code_execution_tool.py child_env — execute_code sandbox

Single source of truth: hermes_constants.get_subprocess_home()

Closes NousResearch#4426
olympus-terminal pushed a commit to olympus-terminal/hermes-agent that referenced this pull request May 16, 2026
…esearch#7357)

Isolate system tool configs (git, ssh, gh, npm) per profile by injecting
a per-profile HOME into subprocess environments only.  The Python
process's own os.environ['HOME'] and Path.home() are never modified,
preserving all existing profile infrastructure.

Activation is directory-based: when {HERMES_HOME}/home/ exists on disk,
subprocesses see it as HOME.  The directory is created automatically for:
- Docker: entrypoint.sh bootstraps it inside the persistent volume
- Named profiles: added to _PROFILE_DIRS in profiles.py

Injection points (all three subprocess env builders):
- tools/environments/local.py _make_run_env() — foreground terminal
- tools/environments/local.py _sanitize_subprocess_env() — background procs
- tools/code_execution_tool.py child_env — execute_code sandbox

Single source of truth: hermes_constants.get_subprocess_home()

Closes NousResearch#4426
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
…esearch#7357)

Isolate system tool configs (git, ssh, gh, npm) per profile by injecting
a per-profile HOME into subprocess environments only.  The Python
process's own os.environ['HOME'] and Path.home() are never modified,
preserving all existing profile infrastructure.

Activation is directory-based: when {HERMES_HOME}/home/ exists on disk,
subprocesses see it as HOME.  The directory is created automatically for:
- Docker: entrypoint.sh bootstraps it inside the persistent volume
- Named profiles: added to _PROFILE_DIRS in profiles.py

Injection points (all three subprocess env builders):
- tools/environments/local.py _make_run_env() — foreground terminal
- tools/environments/local.py _sanitize_subprocess_env() — background procs
- tools/code_execution_tool.py child_env — execute_code sandbox

Single source of truth: hermes_constants.get_subprocess_home()

Closes NousResearch#4426
Egavasyug pushed a commit to Egavasyug/hermes-agent that referenced this pull request Jun 10, 2026
…esearch#7357)

Isolate system tool configs (git, ssh, gh, npm) per profile by injecting
a per-profile HOME into subprocess environments only.  The Python
process's own os.environ['HOME'] and Path.home() are never modified,
preserving all existing profile infrastructure.

Activation is directory-based: when {HERMES_HOME}/home/ exists on disk,
subprocesses see it as HOME.  The directory is created automatically for:
- Docker: entrypoint.sh bootstraps it inside the persistent volume
- Named profiles: added to _PROFILE_DIRS in profiles.py

Injection points (all three subprocess env builders):
- tools/environments/local.py _make_run_env() — foreground terminal
- tools/environments/local.py _sanitize_subprocess_env() — background procs
- tools/code_execution_tool.py child_env — execute_code sandbox

Single source of truth: hermes_constants.get_subprocess_home()

Closes NousResearch#4426
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

1 participant