Python virtual environment variables leaking into subprocesses

NousResearch/hermes-agent · cluster 23473 · apps/desktop

Data as of

Nothing open on GitHub. Fix merged in #53792. Check it covers your case.

The short answer

0open issues
0open PRs
14items grouped
Known?
Yes. 14 related items grouped here, 3 issues already closed.
Fix event
A fix was merged to main in PR #53792. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this partially resolved, deciding PRs #53792, #133868. That is separate from the fix event.
Still open
Nothing open on GitHub.

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.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

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

    merged to main

    files: scripts/release.py, tests/tools/test_local_env_blocklist.py, tools/environments/local.py

    Show the diff
    • scripts/release.py
      @@ -45,6 +45,7 @@
       
       # Auto-extracted from noreply emails + manual overrides
       AUTHOR_MAP = {
      +    "[redacted:email]": "dalenguyen",  # PR #53678 salvage (strip VIRTUAL_ENV/CONDA_PREFIX from terminal subprocess env; #23473)
           "[redacted:email]": "Blaryxoff",  # PR #32602 salvage (deep-merge PUT /api/config to preserve unrelated sections; #13396)
           "[redacted:email]": "DiamondEyesFox",  # PR #53351 salvage (rebaseline in-place compression flushes to prevent duplicate compacted rows; #9096)
           "[redacted:email]": "rlaope",  # PR #35075 salvage (align cron invisible-unicode set with install-time scanner; #35075)
    • tests/tools/test_local_env_blocklist.py
      @@ -239,6 +239,54 @@ def test_force_prefix_overrides_os_environ_block(self):
               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."""
       
    • tools/environments/local.py
      @@ -192,6 +192,18 @@ def _build_provider_env_blocklist() -> frozenset:
       
       _HERMES_PROVIDER_ENV_BLOCKLIST = _build_provider_env_blocklist()
       
      +# Active-virtualenv markers that must NOT leak into terminal subprocesses.
      +# 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 OTHER Python projects, tools like ``uv``/``poetry`` treat
      +# the inherited value as the active environment and build/sync that other
      +# project's dependencies into the Hermes venv path instead of the project's own
      +# ``.venv`` — silently clobbering the Hermes environment (e.g. a project pinned
      +# to a different Python version overwrites it and breaks the gateway). The
      +# Hermes venv stays reachable via PATH (its bin dir is first), so stripping
      +# these markers is safe and only prevents the cross-project clobber (#23473).
      +_ACTIVE_VENV_MARKER_VARS = ("VIRTUAL_ENV", "CONDA_PREFIX")
      +
       
       def _inject_context_hermes_home(env: dict) -> None:
           """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
           from hermes_constants import apply_subprocess_home_env
           apply_subprocess_home_env(sanitized)
       
      +    for _marker in _ACTIVE_VENV_MARKER_VARS:
      +        sanitized.pop(_marker, None)
      +
           return sanitized
       
       
      @@ -528,6 +543,9 @@ def _make_run_env(env: dict) -> dict:
           except Exception:
               pass
       
      +    for _marker in _ACTIVE_VENV_MARKER_VARS:
      +        run_env.pop(_marker, None)
      +
           return run_env
       
       
  • #133868 fix(gateway): stop _ensure_windows_gateway_venv_imports leaking PYTHONPATH into global environment

    merged to main

    files: gateway/run.py, tests/gateway/test_windows_venv_import_guard.py

9 PRs closed without merging

From the close comments: done elsewhere 5 · superseded by another PR 2 · duplicate 1 · merged via another change 1

  • #23474 fix(terminal): strip VIRTUAL_ENV from agent subprocess envs · done elsewhere → #29980
  • #23625 fix(local-env): block virtualenv activation leaks · done elsewhere → #53792, #23473, #53678
  • #23905 fix: strip VIRTUAL_ENV from subprocess environment to avoid venv contamination (#23473) · duplicate → #23474, #23904, #23903
  • #24305 fix(env): Block virtual environment markers from leaking into subprocesses · done elsewhere → #53792, #23473, #53678
  • #39593 fix(gateway): prefer installer-canonical venv/ over .venv in detection fallback · done elsewhere → #53792, #23473, #53678
Show 4 more
  • #41524 fix(desktop): force ad-hoc signing when no real signing identity is configured · done elsewhere → #53792, #23473, #53678
  • #45501 fix: cron model.default fallback + VIRTUAL_ENV leak into subprocesses (P1 bugs) · superseded by another PR → #50011
  • #57470 fix(gateway): stop _ensure_windows_gateway_venv_imports leaking PYTHONPATH into global environment · merged via another change
  • #57854 fix(desktop): avoid leaking Python path to backend · superseded by another PR
Closed issues · 3
  • Closed#23473Gateway leaks VIRTUAL_ENV into subprocesses; agent's `uv sync` in any project bricks Hermes' own venv
  • Closed#57467[Bug]: _ensure_windows_gateway_venv_imports leaks PYTHONPATH into global environment, poisoning non-Hermes Python processes
  • Closed#65909PYTHONPATH leaks to child processes, breaking pipx-installed CLI tools with C extension dependencies
How this cluster was grouped
  • Link evidence (partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
  • Members outside that structural core may belong to a different underlying problem.
  • Pairwise link density 0.13: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Subsystem
apps/desktop
Merged PRs
#53792, #133868
Closed issues
3
Attempts
9 PRs closed without merging
Fix link checked
7 Oct 2026

For your agent

The same facts, typed.