fix(tui): guard slash_worker sys.path against local package shadowing
The slash-command worker is spawned as `-m tui_gateway.slash_worker` and
inherits the user's CWD. A local package in that CWD (e.g. a project shipping
its own `utils/`, `proxy/`, or `ui/`) shadows the installed hermes module, so
`import cli` crashes the worker with:
ImportError: cannot import name 'atomic_replace' from 'utils'
The child then exits 1 in a crash loop. #15989 added this sys.path guard to the
sibling entrypoint tui_gateway/entry.py but not to this worker, which is spawned
as a separate process and so starts with CWD back on sys.path.
Apply the same guard (insert HERMES_PYTHON_SRC_ROOT, strip ''/'.') before the
first non-stdlib import. Add a regression test that imports the worker from a
CWD containing colliding packages.
Fixes #51286
This commit is contained in:
parent
7b45a22ddf
commit
8dcbc910bf
2 changed files with 75 additions and 2 deletions
60
tests/tui_gateway/test_slash_worker_sys_path.py
Normal file
60
tests/tui_gateway/test_slash_worker_sys_path.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
"""Regression tests for tui_gateway/slash_worker.py sys.path hardening (issue #51286).
|
||||
|
||||
The slash-command worker is spawned as ``-m tui_gateway.slash_worker`` and
|
||||
inherits the user's CWD. A local package (e.g. ``utils/``) in that CWD shadows
|
||||
the installed hermes ``utils`` module and crashes the worker on ``import cli``
|
||||
(``ImportError: cannot import name 'atomic_replace' from 'utils'``).
|
||||
|
||||
#15989 added this guard to the sibling entrypoint ``tui_gateway/entry.py`` but
|
||||
missed this child, so the crash still reproduced. slash_worker.py must sanitize
|
||||
sys.path before its first non-stdlib import.
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def test_slash_worker_imports_from_cwd_with_colliding_utils(tmp_path):
|
||||
"""Importing the worker from a CWD that ships its own ``utils/`` package
|
||||
must succeed — the guard strips CWD so the installed module wins."""
|
||||
# Mimic the user's project (tg-ws-proxy ships utils/, proxy/, ui/).
|
||||
for pkg in ("utils", "proxy", "ui"):
|
||||
(tmp_path / pkg).mkdir()
|
||||
(tmp_path / pkg / "__init__.py").write_text("") # no atomic_replace, etc.
|
||||
|
||||
env = {k: v for k, v in os.environ.items() if k != "HERMES_PYTHON_SRC_ROOT"}
|
||||
# Keep the source importable via PYTHONPATH; CWD ('') still precedes it on
|
||||
# sys.path for ``-c``, so the shadow (and thus the guard) is still exercised.
|
||||
env["PYTHONPATH"] = str(PROJECT_ROOT)
|
||||
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-c", "import tui_gateway.slash_worker"],
|
||||
cwd=tmp_path,
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120,
|
||||
)
|
||||
|
||||
assert result.returncode == 0, (
|
||||
"slash_worker failed to import from a CWD containing a colliding "
|
||||
"utils/ package — sys.path guard regressed (issue #51286).\n"
|
||||
f"stderr:\n{result.stderr}"
|
||||
)
|
||||
|
||||
|
||||
def test_sys_path_guard_runs_before_cli_import():
|
||||
"""The guard must execute before ``import cli`` — reordering it below the
|
||||
import would re-introduce the shadowing crash."""
|
||||
src = (PROJECT_ROOT / "tui_gateway" / "slash_worker.py").read_text()
|
||||
guard = 'sys.path = [p for p in sys.path if p not in {"", "."}]'
|
||||
cli_import = "import cli as cli_mod"
|
||||
assert guard in src, "sys.path shadowing guard missing from slash_worker.py"
|
||||
assert cli_import in src, "expected 'import cli as cli_mod' in slash_worker.py"
|
||||
assert src.index(guard) < src.index(cli_import), (
|
||||
"sys.path guard must run before 'import cli' (issue #51286)"
|
||||
)
|
||||
|
|
@ -3,12 +3,25 @@
|
|||
Protocol: reads JSON lines from stdin {id, command}, writes {id, ok, output|error} to stdout.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Guard against a local ``utils/`` (or other) package in the spawn CWD shadowing
|
||||
# installed hermes modules. This worker is spawned as ``-m tui_gateway.slash_worker``
|
||||
# and inherits the user's CWD, so the ``import cli`` below would otherwise resolve
|
||||
# ``utils`` to a colliding local package and crash the child (issue #51286). The
|
||||
# sibling entrypoint ``tui_gateway/entry.py`` applies the same guard; #15989 added
|
||||
# it there but missed this child.
|
||||
_src_root = os.environ.get("HERMES_PYTHON_SRC_ROOT", "")
|
||||
if _src_root and _src_root not in sys.path:
|
||||
sys.path.insert(0, _src_root)
|
||||
# '' and '.' both resolve to CWD at import time and can shadow installed packages.
|
||||
sys.path = [p for p in sys.path if p not in {"", "."}]
|
||||
|
||||
import argparse
|
||||
import contextlib
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue