mirror of
https://github.com/esphome/esphome.git
synced 2026-08-17 19:13:18 +08:00
[core] fix PYTHONPATH leak (#18360)
This commit is contained in:
@@ -109,6 +109,8 @@ def _get_idf_env(version: str | None = None) -> dict[str, str]:
|
||||
env_cache = _cache().env
|
||||
if version not in env_cache:
|
||||
env_cache[version] = os.environ.copy()
|
||||
# Do not leak PYTHONPATH into child env
|
||||
env_cache[version].pop("PYTHONPATH", None)
|
||||
|
||||
# Use provided IDF framework if available
|
||||
if "IDF_PATH" not in os.environ:
|
||||
|
||||
@@ -155,6 +155,8 @@ def run_command(
|
||||
_LOGGER.debug("%s - running ...", cmd_str)
|
||||
|
||||
run_env = os.environ.copy()
|
||||
# Do not leak PYTHONPATH
|
||||
run_env.pop("PYTHONPATH", None)
|
||||
if env:
|
||||
run_env.update(env)
|
||||
|
||||
|
||||
@@ -265,6 +265,21 @@ def test_get_idf_env_sets_git_ceiling_directories(setup_core: Path) -> None:
|
||||
assert str(CORE.config_dir) in env["GIT_CEILING_DIRECTORIES"].split(os.pathsep)
|
||||
|
||||
|
||||
def test_get_idf_env_pops_inherited_pythonpath(setup_core: Path) -> None:
|
||||
"""A PYTHONPATH from the parent environment must not reach idf.py.
|
||||
|
||||
It would override the IDF venv's isolation, shadowing its pinned
|
||||
packages and failing idf.py's dependency check.
|
||||
"""
|
||||
toolchain._cache().env.clear()
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{"IDF_PATH": str(setup_core), "PYTHONPATH": "/outside/site-packages"},
|
||||
):
|
||||
env = toolchain._get_idf_env(version="5.5.4")
|
||||
assert "PYTHONPATH" not in env
|
||||
|
||||
|
||||
def test_get_cmake_output_without_build_dir(setup_core: Path) -> None:
|
||||
"""A build dir that was never created raises EsphomeError.
|
||||
|
||||
|
||||
@@ -188,6 +188,24 @@ def test_run_command_passes_env(mock_subprocess_run: Mock) -> None:
|
||||
assert mock_subprocess_run.call_args[1]["env"]["MY_VAR"] == "42"
|
||||
|
||||
|
||||
def test_run_command_pops_inherited_pythonpath(mock_subprocess_run: Mock) -> None:
|
||||
"""A PYTHONPATH from the parent environment must not leak into subprocesses."""
|
||||
mock_subprocess_run.return_value = Mock(returncode=0, stdout="", stderr="")
|
||||
with patch.dict(os.environ, {"PYTHONPATH": "/outside/site-packages"}):
|
||||
run_command(["cmd"])
|
||||
assert "PYTHONPATH" not in mock_subprocess_run.call_args[1]["env"]
|
||||
|
||||
|
||||
def test_run_command_env_pythonpath_preferred_over_pop(
|
||||
mock_subprocess_run: Mock,
|
||||
) -> None:
|
||||
"""A PYTHONPATH set explicitly via ``env`` is passed through."""
|
||||
mock_subprocess_run.return_value = Mock(returncode=0, stdout="", stderr="")
|
||||
with patch.dict(os.environ, {"PYTHONPATH": "/outside/site-packages"}):
|
||||
run_command(["cmd"], env={"PYTHONPATH": "/idf/tools"})
|
||||
assert mock_subprocess_run.call_args[1]["env"]["PYTHONPATH"] == "/idf/tools"
|
||||
|
||||
|
||||
def test_run_command_passes_cwd(mock_subprocess_run: Mock, tmp_path: Path) -> None:
|
||||
mock_subprocess_run.return_value = Mock(returncode=0, stdout="", stderr="")
|
||||
run_command(["cmd"], cwd=str(tmp_path))
|
||||
|
||||
Reference in New Issue
Block a user