fix(agent): inject process HERMES_HOME into subprocess env when context override unset#38308
Open
spiky02plateau wants to merge 1 commit into
Open
Conversation
…xt override unset
Subprocess env builders (_sanitize_subprocess_env, _make_run_env) pin
HOME to the profile's home/ dir but only inject HERMES_HOME from the
_HERMES_HOME_OVERRIDE ContextVar, which is unset for background/PTY/cron
spawns. The child then has HOME={profile}/home but no HERMES_HOME, so
get_hermes_home() falls back to ~/.hermes and reads the default profile's
config/auth/memory instead of its own — cross-profile data corruption.
Add a single os.getenv("HERMES_HOME") fallback in the shared
_inject_context_hermes_home() so the common single-profile-gateway case
is covered. The ContextVar keeps precedence (per-session profile
mutation, NousResearch#1976); only one key (HERMES_HOME, a non-secret path) is
touched, so the secret-isolation invariant is intact.
Fixes NousResearch#4707
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Subprocesses spawned under a profile-scoped gateway lose their
HERMES_HOME, so the child resolvesget_hermes_home()to the default~/.hermesand reads the wrong profile's config/auth/memory — the cross-profile data-corruption / isolation break tracked in #4707.Both subprocess env builders in
tools/environments/local.py—_sanitize_subprocess_env(background + PTY spawns) and_make_run_env— pinHOMEto the profile'shome/dir, but injectHERMES_HOMEonly from the_HERMES_HOME_OVERRIDEContextVar via_inject_context_hermes_home(). That ContextVar is unset for the common background/PTY/cron spawns, so the child ends up withHOME={profile}/homeand noHERMES_HOME._sanitize_subprocess_envmakes this worse because it builds frombase_env or {}(notos.environ), so even the parent's ownHERMES_HOMEdoesn't carry through.This fixes it centrally, at the one shared seam both builders already call, rather than adding yet another per-spawner pin. A single
os.getenv("HERMES_HOME")fallback in_inject_context_hermes_home()covers the common single-profile-gateway case while preserving ContextVar precedence:Why this shape (vs. another per-spawner fix)
The repo already carries several narrow, per-call-site
HERMES_HOME/HOME propagation fixes (e.g. #4729, #15330, #36742, #25151, #27260), and #4707 keeps re-surfacing because each new spawner re-opens the same class of bug._inject_context_hermes_home()is the single seam both env builders funnel through, so fixing it here closes the current spawners and future ones in one place — and subsumes the narrower open PRs against #4707.Security / isolation
This is the secure direction, not a leak:
HERMES_HOME, a non-secret path var — inside the helper whose sole job is already to inject that one key. The secret-stripping machinery (_HERMES_PROVIDER_ENV_BLOCKLIST,is_env_passthrough, the_HERMES_FORCE_allowlist) and thebase_env or {}build are untouched — zero new secret-exposure surface.get_hermes_home_override() or …), so multi-profile-per-process sessions that mutate the home per session (fix(agent): prevent silent tool result loss during context compression #1976) still get the right value;os.getenvis only the fallback when the ContextVar was never set.auth.json/config/memory. Pinning the correctHERMES_HOMEshrinks blast radius — a compromised/injected profile-scoped skill stays in its own profile instead of reaching the default profile.One honest edge: multi-profile-in-one-process with the ContextVar unset →
os.getenvreturns the process's startupHERMES_HOME. But the override still wins when set, so this only hits the unset case, and even then it's strictly no worse than today (today injects nothing → default fallback). Happy to instead pin directly in_sanitize_subprocess_envif maintainers prefer keeping the helper ContextVar-only.Related Issue
Fixes #4707
Type of Change
Changes Made
tools/environments/local.py—_inject_context_hermes_home(): fall back toos.getenv("HERMES_HOME")when the context override is unset (ContextVar still wins). One-line behavior change + explanatory comment.tests/test_subprocess_home_isolation.py— addtest_process_hermes_home_bridges_when_context_unsetto bothTestSanitizeSubprocessEnvHomeInjectionandTestMakeRunEnvHomeInjection, exercising the [Bug]: cron under profile-scoped launchd gateway falls back to default~/.hermesinstead of profileHERMES_HOME#4707 pattern (ContextVar unset, profilehome/exists).How to Test
_sanitize_subprocess_envtest fails onmain(KeyError: 'HERMES_HOME') and passes with this change — that delta is the bug and the fix.uv run pytest tests/test_subprocess_home_isolation.py tests/tools/test_local_env_blocklist.py -q→ all pass (40).uv run ruff check tools/environments/local.py tests/test_subprocess_home_isolation.py→ clean.Checklist
Code
fix(agent): …)Documentation & Housekeeping