{"repo":"NousResearch/hermes-agent","public_id":"23473","display_label":"Python virtual environment variables leaking into subprocesses","summary":"Multiple issues and fixes address VIRTUAL_ENV, CONDA_PREFIX, and PYTHONPATH environment variables leaking from the application into child processes, contaminating unrelated Python environments and tools. A few unrelated fixes (Windows signing, cron fallback) are also present in the group.","subsystem":"apps/desktop","counts":{"members":14,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":3,"merged_prs":2,"attempt_prs":9},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":53792},"resolution":{"state":"partially_resolved","by":"pr-53792,pr-133868"},"freshness":{"state":"verified","last_verified_at":"2026-10-07T21:19:03+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.12879120879120878},"fix_edges":[{"issue_number":23473,"pr_number":53792,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":57467,"pr_number":133868,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":23473,"title":"Gateway leaks VIRTUAL_ENV into subprocesses; agent's `uv sync` in any project bricks Hermes' own venv","url":null,"state":"closed"},{"kind":"issue","number":57467,"title":"[Bug]: _ensure_windows_gateway_venv_imports leaks PYTHONPATH into global environment, poisoning non-Hermes Python processes","url":null,"state":"closed"},{"kind":"issue","number":65909,"title":"PYTHONPATH leaks to child processes, breaking pipx-installed CLI tools with C extension dependencies","url":null,"state":"closed"},{"kind":"pr","number":23474,"title":"fix(terminal): strip VIRTUAL_ENV from agent subprocess envs","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#29980","number":29980}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":23625,"title":"fix(local-env): block virtualenv activation leaks","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#53792","number":53792},{"kind":"pr","value":"#23473","number":23473},{"kind":"pr","value":"#53678","number":53678}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":23905,"title":"fix: strip VIRTUAL_ENV from subprocess environment to avoid venv contamination (#23473)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#23474","number":23474},{"kind":"pr","value":"#23904","number":23904},{"kind":"pr","value":"#23903","number":23903}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":24305,"title":"fix(env): Block virtual environment markers from leaking into subprocesses","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#53792","number":53792},{"kind":"pr","value":"#23473","number":23473},{"kind":"pr","value":"#53678","number":53678}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":39593,"title":"fix(gateway): prefer installer-canonical venv/ over .venv in detection fallback","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#53792","number":53792},{"kind":"pr","value":"#23473","number":23473},{"kind":"pr","value":"#53678","number":53678}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":41524,"title":"fix(desktop): force ad-hoc signing when no real signing identity is configured","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#53792","number":53792},{"kind":"pr","value":"#23473","number":23473},{"kind":"pr","value":"#53678","number":53678}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":45501,"title":"fix: cron model.default fallback + VIRTUAL_ENV leak into subprocesses (P1 bugs)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#50011","number":50011}],"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":53792,"title":"fix(terminal): strip VIRTUAL_ENV/CONDA_PREFIX from terminal subprocess env","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["scripts/release.py","tests/tools/test_local_env_blocklist.py","tools/environments/local.py"],"fix_hunks":[{"file":"scripts/release.py","hunks":["@@ -45,6 +45,7 @@\n \n # Auto-extracted from noreply emails + manual overrides\n AUTHOR_MAP = {\n+    \"[redacted:email]\": \"dalenguyen\",  # PR #53678 salvage (strip VIRTUAL_ENV/CONDA_PREFIX from terminal subprocess env; #23473)\n     \"[redacted:email]\": \"Blaryxoff\",  # PR #32602 salvage (deep-merge PUT /api/config to preserve unrelated sections; #13396)\n     \"[redacted:email]\": \"DiamondEyesFox\",  # PR #53351 salvage (rebaseline in-place compression flushes to prevent duplicate compacted rows; #9096)\n     \"[redacted:email]\": \"rlaope\",  # PR #35075 salvage (align cron invisible-unicode set with install-time scanner; #35075)"]},{"file":"tests/tools/test_local_env_blocklist.py","hunks":["@@ -239,6 +239,54 @@ def test_force_prefix_overrides_os_environ_block(self):\n         assert result_env[\"OPENAI_BASE_URL\"] == \"http://intended/v1\"\n \n \n+class TestActiveVenvMarkerStripping:\n+    \"\"\"Active-virtualenv markers must not leak into terminal subprocesses (#23473).\n+\n+    The gateway runs inside its own venv, so its process environment carries\n+    VIRTUAL_ENV (and possibly CONDA_PREFIX). If those leak into commands the\n+    agent runs against ANOTHER Python project, ``uv``/``poetry`` treat the\n+    inherited value as the active environment and build that project's deps\n+    into the Hermes venv path instead of the project's own ``.venv`` —\n+    silently clobbering the Hermes environment (and, when the other project\n+    pins a different Python, breaking the gateway outright). The Hermes venv\n+    stays reachable via PATH, so stripping the markers is safe.\n+    \"\"\"\n+\n+    def test_virtualenv_marker_stripped_end_to_end(self):\n+        result_env = _run_with_env(extra_os_env={\n+            \"VIRTUAL_ENV\": \"/home/user/.hermes/hermes-agent/venv\",\n+        })\n+        assert \"VIRTUAL_ENV\" not in result_env\n+\n+    def test_conda_prefix_marker_stripped_end_to_end(self):\n+        result_env = _run_with_env(extra_os_env={\n+            \"CONDA_PREFIX\": \"/opt/conda/envs/hermes\",\n+        })\n+        assert \"CONDA_PREFIX\" not in result_env\n+\n+    def test_make_run_env_strips_markers(self):\n+        from tools.environments.local import _make_run_env\n+        poison = {\"VIRTUAL_ENV\": \"/venv\", \"CONDA_PREFIX\": \"/conda\", \"PATH\": \"/usr/bin\"}\n+        with patch.dict(os.environ, poison, clear=True):\n+            result = _make_run_env({})\n+        assert \"VIRTUAL_ENV\" not in result\n+        assert \"CONDA_PREFIX\" not in result\n+\n+    def test_sanitize_subprocess_env_strips_markers(self):\n+        from tools.environments.local import _sanitize_subprocess_env\n+        base = {\"VIRTUAL_ENV\": \"/venv\", \"CONDA_PREFIX\": \"/conda\", \"HOME\": \"/home/user\"}\n+        # Even an explicitly-passed extra marker is stripped.\n+        result = _sanitize_subprocess_env(base, {\"VIRTUAL_ENV\": \"/also/venv\"})\n+        assert \"VIRTUAL_ENV\" not in result\n+        assert \"CONDA_PREFIX\" not in result\n+        assert result.get(\"HOME\") == \"/home/user\"\n+\n+    def test_markers_constant_contents(self):\n+        from tools.environments.local import _ACTIVE_VENV_MARKER_VARS\n+        assert \"VIRTUAL_ENV\" in _ACTIVE_VENV_MARKER_VARS\n+        assert \"CONDA_PREFIX\" in _ACTIVE_VENV_MARKER_VARS\n+\n+\n class TestBlocklistCoverage:\n     \"\"\"Sanity checks that the blocklist covers all known providers.\"\"\"\n "]},{"file":"tools/environments/local.py","hunks":["@@ -192,6 +192,18 @@ def _build_provider_env_blocklist() -> frozenset:\n \n _HERMES_PROVIDER_ENV_BLOCKLIST = _build_provider_env_blocklist()\n \n+# Active-virtualenv markers that must NOT leak into terminal subprocesses.\n+# The gateway runs inside its own venv, so its process environment carries\n+# VIRTUAL_ENV (and possibly CONDA_PREFIX). If those leak into commands the\n+# agent runs against OTHER Python projects, tools like ``uv``/``poetry`` treat\n+# the inherited value as the active environment and build/sync that other\n+# project's dependencies into the Hermes venv path instead of the project's own\n+# ``.venv`` — silently clobbering the Hermes environment (e.g. a project pinned\n+# to a different Python version overwrites it and breaks the gateway). The\n+# Hermes venv stays reachable via PATH (its bin dir is first), so stripping\n+# these markers is safe and only prevents the cross-project clobber (#23473).\n+_ACTIVE_VENV_MARKER_VARS = (\"VIRTUAL_ENV\", \"CONDA_PREFIX\")\n+\n \n def _inject_context_hermes_home(env: dict) -> None:\n     \"\"\"Bridge the context-local Hermes home override into subprocess env.\"\"\"","@@ -232,6 +244,9 @@ def _sanitize_subprocess_env(base_env: dict | None, extra_env: dict | None = Non\n     from hermes_constants import apply_subprocess_home_env\n     apply_subprocess_home_env(sanitized)\n \n+    for _marker in _ACTIVE_VENV_MARKER_VARS:\n+        sanitized.pop(_marker, None)\n+\n     return sanitized\n \n ","@@ -528,6 +543,9 @@ def _make_run_env(env: dict) -> dict:\n     except Exception:\n         pass\n \n+    for _marker in _ACTIVE_VENV_MARKER_VARS:\n+        run_env.pop(_marker, None)\n+\n     return run_env\n \n \n"]}]},{"kind":"pr","number":57470,"title":"fix(gateway): stop _ensure_windows_gateway_venv_imports leaking PYTHONPATH into global environment","url":null,"state":"closed","close_reason":{"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":57854,"title":"fix(desktop): avoid leaking Python path to backend","url":null,"state":"closed","close_reason":{"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":133868,"title":"fix(gateway): stop _ensure_windows_gateway_venv_imports leaking PYTHONPATH into global environment","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["gateway/run.py","tests/gateway/test_windows_venv_import_guard.py"]}],"trust":{"default_tier":"verified","includes_suggested":false}}