diff --git a/pythonlib/camoufox/utils.py b/pythonlib/camoufox/utils.py index 7282856..2f75d95 100644 --- a/pythonlib/camoufox/utils.py +++ b/pythonlib/camoufox/utils.py @@ -6,7 +6,7 @@ from os.path import abspath from pathlib import Path from pprint import pprint from random import randint, randrange -from typing import Any, Dict, List, Literal, Optional, Tuple, Union, cast +from typing import Any, Dict, List, Literal, Optional, Tuple, Union import numpy as np import orjson @@ -601,8 +601,10 @@ def launch_options( custom_fonts_only = False if i_know_what_im_doing is None: i_know_what_im_doing = False - if env is None: - env = cast(Dict[str, Union[str, float, bool]], environ) + # Keep per-launch overrides isolated from the process environment and from + # mappings supplied by callers. In particular, DISPLAY must not outlive the + # virtual display that owns it. + env = dict(environ) if env is None else dict(env) if isinstance(executable_path, str): # Convert executable path to a Path object executable_path = Path(abspath(executable_path)) diff --git a/pythonlib/tests/test_launch_environment.py b/pythonlib/tests/test_launch_environment.py new file mode 100644 index 0000000..b462528 --- /dev/null +++ b/pythonlib/tests/test_launch_environment.py @@ -0,0 +1,76 @@ +"""Regression tests for per-launch environment isolation.""" + +import os + +import pytest + +from camoufox import utils + + +@pytest.fixture +def isolated_launch_dependencies(monkeypatch): + """Keep launch_options focused on environment assembly, without a browser.""" + monkeypatch.setattr(utils, "add_default_addons", lambda *args, **kwargs: None) + monkeypatch.setattr(utils, "generate_fingerprint", lambda *args, **kwargs: object()) + monkeypatch.setattr(utils, "from_browserforge", lambda *args, **kwargs: {}) + monkeypatch.setattr(utils, "get_screen_cons", lambda *args, **kwargs: None) + monkeypatch.setattr(utils, "_generate_random_font_subset", lambda *args: []) + monkeypatch.setattr(utils, "_generate_random_voice_subset", lambda *args: []) + monkeypatch.setattr(utils, "fix_navigator_arch", lambda *args: None) + monkeypatch.setattr(utils, "fix_screen_no_taskbar", lambda *args: None) + monkeypatch.setattr(utils, "clamp_window_dimensions", lambda *args: None) + monkeypatch.setattr(utils, "set_media_devices_defaults", lambda *args: None) + monkeypatch.setattr(utils, "installed_verstr", lambda: "152.0.4-beta.28") + monkeypatch.setattr(utils, "validate_config", lambda *args, **kwargs: None) + monkeypatch.setattr(utils, "get_env_vars", lambda *args, **kwargs: {}) + monkeypatch.setattr(utils, "launch_path", lambda: "/test/camoufox") + + +def _launch_with_virtual_display(**kwargs): + return utils.launch_options( + virtual_display=":4242", + headless=True, + block_webgl=True, + i_know_what_im_doing=True, + **kwargs, + ) + + +def test_virtual_display_does_not_mutate_process_environment( + monkeypatch, isolated_launch_dependencies +): + monkeypatch.delenv("DISPLAY", raising=False) + monkeypatch.setenv("GDK_BACKEND", "wayland") + monkeypatch.setenv("WAYLAND_DISPLAY", "wayland-0") + monkeypatch.setenv("MOZ_ENABLE_WAYLAND", "1") + keys = ("DISPLAY", "GDK_BACKEND", "WAYLAND_DISPLAY", "MOZ_ENABLE_WAYLAND") + before = {key: os.environ.get(key) for key in keys} + + options = _launch_with_virtual_display() + + assert {key: os.environ.get(key) for key in keys} == before + assert options["env"]["DISPLAY"] == ":4242" + assert options["env"]["GDK_BACKEND"] == "x11" + assert "WAYLAND_DISPLAY" not in options["env"] + assert options["env"]["MOZ_ENABLE_WAYLAND"] == "0" + + +def test_virtual_display_does_not_mutate_caller_environment( + isolated_launch_dependencies, +): + caller_env = { + "UNCHANGED": "value", + "GDK_BACKEND": "wayland", + "WAYLAND_DISPLAY": "wayland-0", + "MOZ_ENABLE_WAYLAND": "1", + } + before = caller_env.copy() + + options = _launch_with_virtual_display(env=caller_env) + + assert caller_env == before + assert options["env"]["UNCHANGED"] == "value" + assert options["env"]["DISPLAY"] == ":4242" + assert options["env"]["GDK_BACKEND"] == "x11" + assert "WAYLAND_DISPLAY" not in options["env"] + assert options["env"]["MOZ_ENABLE_WAYLAND"] == "0"