diff --git a/tests/tools/test_browser_ssrf_local.py b/tests/tools/test_browser_ssrf_local.py index 691f9256f2b..9536e09891d 100644 --- a/tests/tools/test_browser_ssrf_local.py +++ b/tests/tools/test_browser_ssrf_local.py @@ -190,6 +190,39 @@ class TestIsLocalBackend: assert browser_tool._is_local_backend() is False + @pytest.mark.parametrize("backend", ["docker", "modal", "daytona", "ssh", "singularity"]) + def test_container_terminal_backend_is_not_local(self, monkeypatch, backend): + """Terminal running in a container → NOT local (browser on host can access internal networks).""" + monkeypatch.setattr(browser_tool, "_is_camofox_mode", lambda: False) + monkeypatch.setattr(browser_tool, "_get_cloud_provider", lambda: None) + monkeypatch.setenv("TERMINAL_ENV", backend) + + assert browser_tool._is_local_backend() is False + + def test_empty_terminal_env_is_local(self, monkeypatch): + """Empty TERMINAL_ENV → local backend.""" + monkeypatch.setattr(browser_tool, "_is_camofox_mode", lambda: False) + monkeypatch.setattr(browser_tool, "_get_cloud_provider", lambda: None) + monkeypatch.setenv("TERMINAL_ENV", "") + + assert browser_tool._is_local_backend() is True + + def test_local_terminal_env_is_local(self, monkeypatch): + """Explicit 'local' TERMINAL_ENV → local backend.""" + monkeypatch.setattr(browser_tool, "_is_camofox_mode", lambda: False) + monkeypatch.setattr(browser_tool, "_get_cloud_provider", lambda: None) + monkeypatch.setenv("TERMINAL_ENV", "local") + + assert browser_tool._is_local_backend() is True + + def test_camofox_overrides_container_backend(self, monkeypatch): + """Camofox mode always counts as local, even with container terminal.""" + monkeypatch.setattr(browser_tool, "_is_camofox_mode", lambda: True) + monkeypatch.setattr(browser_tool, "_get_cloud_provider", lambda: None) + monkeypatch.setenv("TERMINAL_ENV", "docker") + + assert browser_tool._is_local_backend() is True + # --------------------------------------------------------------------------- # Post-redirect SSRF check diff --git a/tools/browser_tool.py b/tools/browser_tool.py index ee597d50c0f..90975175786 100644 --- a/tools/browser_tool.py +++ b/tools/browser_tool.py @@ -619,7 +619,7 @@ def _is_local_mode() -> bool: def _is_local_backend() -> bool: - """Return True when the browser runs locally (no cloud provider). + """Return True when the browser runs locally AND the terminal is also local. SSRF protection is only meaningful for cloud backends (Browserbase, BrowserUse) where the agent could reach internal resources on a remote @@ -627,8 +627,20 @@ def _is_local_backend() -> bool: Chromium without a cloud provider — the user already has full terminal and network access on the same machine, so the check adds no security value. + + However, when the terminal runs in a container (docker, modal, daytona, + ssh, singularity), the browser on the host can access internal networks + that the terminal cannot. In this case, SSRF protection should be + enabled even though the browser is technically "local". """ - return _is_camofox_mode() or _get_cloud_provider() is None + if _is_camofox_mode(): + return True + if _get_cloud_provider() is not None: + return False + # When terminal runs in a container, browser on host can access + # internal networks the terminal can't → treat as non-local. + terminal_backend = os.getenv("TERMINAL_ENV", "local").strip().lower() + return terminal_backend in ("local", "") _auto_local_for_private_urls_resolved = False