fix(terminal): strip VIRTUAL_ENV/CONDA_PREFIX from terminal subprocess env

The Hermes gateway runs inside its own venv, so its process environment
carries VIRTUAL_ENV (and possibly CONDA_PREFIX). The terminal tool spawned
subprocesses inheriting those markers. When the agent ran `uv sync`,
`uv pip install`, `poetry install`, etc. in ANY other project directory,
those tools honored the inherited VIRTUAL_ENV and rebuilt/synced that
project's dependencies into the Hermes venv path — wiping Hermes' own runtime
deps (and, when the other project pinned a different Python, replacing the
interpreter), bricking the gateway on the next restart (#23473).

Strip VIRTUAL_ENV/CONDA_PREFIX in both subprocess-env construction points in
tools/environments/local.py — `_sanitize_subprocess_env` and `_make_run_env`
— via a shared `_ACTIVE_VENV_MARKER_VARS` constant. The Hermes venv stays
reachable because its bin dir is already first on PATH, so removing the
active-environment markers is safe and only prevents the cross-project clobber.

Adds TestActiveVenvMarkerStripping: end-to-end (markers in os.environ don't
reach the spawned subprocess) and unit coverage for both functions, plus a
guard on the marker constant.

Also adds the AUTHOR_MAP entry for the salvaged contributor.

Closes #23473
This commit is contained in:
Dale Nguyen 2026-06-28 00:54:22 +05:30 committed by kshitij
parent d470ed0c4c
commit dbbf102b8e
3 changed files with 67 additions and 0 deletions

View file

@ -239,6 +239,54 @@ class TestForceEnvOptIn:
assert result_env["OPENAI_BASE_URL"] == "http://intended/v1"
class TestActiveVenvMarkerStripping:
"""Active-virtualenv markers must not leak into terminal subprocesses (#23473).
The gateway runs inside its own venv, so its process environment carries
VIRTUAL_ENV (and possibly CONDA_PREFIX). If those leak into commands the
agent runs against ANOTHER Python project, ``uv``/``poetry`` treat the
inherited value as the active environment and build that project's deps
into the Hermes venv path instead of the project's own ``.venv`` —
silently clobbering the Hermes environment (and, when the other project
pins a different Python, breaking the gateway outright). The Hermes venv
stays reachable via PATH, so stripping the markers is safe.
"""
def test_virtualenv_marker_stripped_end_to_end(self):
result_env = _run_with_env(extra_os_env={
"VIRTUAL_ENV": "/home/user/.hermes/hermes-agent/venv",
})
assert "VIRTUAL_ENV" not in result_env
def test_conda_prefix_marker_stripped_end_to_end(self):
result_env = _run_with_env(extra_os_env={
"CONDA_PREFIX": "/opt/conda/envs/hermes",
})
assert "CONDA_PREFIX" not in result_env
def test_make_run_env_strips_markers(self):
from tools.environments.local import _make_run_env
poison = {"VIRTUAL_ENV": "/venv", "CONDA_PREFIX": "/conda", "PATH": "/usr/bin"}
with patch.dict(os.environ, poison, clear=True):
result = _make_run_env({})
assert "VIRTUAL_ENV" not in result
assert "CONDA_PREFIX" not in result
def test_sanitize_subprocess_env_strips_markers(self):
from tools.environments.local import _sanitize_subprocess_env
base = {"VIRTUAL_ENV": "/venv", "CONDA_PREFIX": "/conda", "HOME": "/home/user"}
# Even an explicitly-passed extra marker is stripped.
result = _sanitize_subprocess_env(base, {"VIRTUAL_ENV": "/also/venv"})
assert "VIRTUAL_ENV" not in result
assert "CONDA_PREFIX" not in result
assert result.get("HOME") == "/home/user"
def test_markers_constant_contents(self):
from tools.environments.local import _ACTIVE_VENV_MARKER_VARS
assert "VIRTUAL_ENV" in _ACTIVE_VENV_MARKER_VARS
assert "CONDA_PREFIX" in _ACTIVE_VENV_MARKER_VARS
class TestBlocklistCoverage:
"""Sanity checks that the blocklist covers all known providers."""