From 5de50ab297490e24bf546247baf9109e1752d19c Mon Sep 17 00:00:00 2001 From: Naman Arora Date: Mon, 2 Nov 2026 20:00:55 +0530 Subject: [PATCH] fix(manual): say that Chrome capture starts with a fresh profile Manual capture printed "using real chrome (profile copy)" and the class docstring promised the user's existing profile, but _start_with_real_chrome has always launched Chrome on an empty temporary profile: the user's profile directory is only checked to see that Chrome is installed. Users expecting their logins found themselves signed out with no explanation. Make the message, docstrings and manual-mode docs say the browser starts with a fresh profile and that sign-in happens during the capture. Copying the real profile is left out on purpose: it is large, locked while Chrome runs, and would duplicate the user's cookies and saved credentials into a temp dir. --- CHANGELOG.md | 1 + src/reverse_api/browser.py | 10 +++++--- tests/test_manual_browser_profile.py | 36 +++++++++++++++++++++++++++ website/content/docs/modes/manual.mdx | 2 ++ 4 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 tests/test_manual_browser_profile.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 6aa65ad..fa40382 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `agent --json` and `engineer --json` report `status: error` (`engine_failure`) when the run ends without writing its client, instead of `ok` with a `script_path` that does not exist. - Playwright MCP prompt: `browser_run_code` is now `browser_run_code_unsafe`, and the nonexistent `browser_scroll` tool is no longer listed. - The Claude SDK message buffer is raised to 32 MB, so a large screenshot no longer aborts a run. +- Manual capture in Chrome no longer says it uses a "profile copy". It has always started Chrome with a fresh, empty profile, so the message and docs now say to sign in during the capture. ### Security - Raise security minimums to aiohttp >=3.14.4, certifi >=2026.10.19, multidict >=6.9.1, OpenTelemetry API >=1.45.1, PyJWT >=2.15.0 and urllib3 >=2.8.0, and lock the patched versions. certifi drops the emSign root CAs whose intermediate keys were compromised. diff --git a/src/reverse_api/browser.py b/src/reverse_api/browser.py index 1249c06..657a4e6 100644 --- a/src/reverse_api/browser.py +++ b/src/reverse_api/browser.py @@ -184,7 +184,7 @@ class ManualBrowser: """Manages a Playwright browser session with HAR recording. Supports two modes: - - Real Chrome: Uses your actual Chrome browser with existing profile (best for stealth) + - Real Chrome: Your installed Chrome with a fresh, empty profile (best for stealth; starts signed out) - Stealth Chromium: Falls back to Playwright's Chromium with stealth patches """ @@ -264,7 +264,11 @@ def _require_playwright(self) -> Playwright: return self._playwright def _start_with_real_chrome(self, start_url: str | None = None) -> Path: - """Start using the real Chrome browser with user's profile.""" + """Start the installed Chrome with a fresh temporary profile. + + The user's Chrome profile only shows that Chrome is installed; it is not copied, + so the capture starts without the user's logins and cookies. + """ import shutil import tempfile @@ -276,7 +280,7 @@ def _start_with_real_chrome(self, start_url: str | None = None) -> Path: # Create a temporary profile directory temp_profile_dir = Path(tempfile.mkdtemp(prefix="chrome_profile_")) - console.print(" [dim]using real chrome (profile copy)[/dim]") + console.print(" [dim]using chrome with a fresh profile · sign in during the capture if the site needs it[/dim]") console.print(" [yellow]⚠️ please browse in the FIRST tab only[/yellow]") console.print(" [yellow] (new tabs may not be recorded)[/yellow]") console.print() diff --git a/tests/test_manual_browser_profile.py b/tests/test_manual_browser_profile.py new file mode 100644 index 0000000..29f4437 --- /dev/null +++ b/tests/test_manual_browser_profile.py @@ -0,0 +1,36 @@ +"""Manual capture in real Chrome uses a fresh profile, not the user's own. + +The user's Chrome profile directory is only checked to see that Chrome is installed. +Nothing is copied from it, so the capture starts signed out and the message must say so. +""" + +from unittest.mock import MagicMock, patch + +import pytest + +pytest.importorskip("playwright") # ships in the [manual] extra only + +from reverse_api.browser import ManualBrowser # noqa: E402 + + +def test_real_chrome_launches_with_a_fresh_profile_and_says_so(tmp_path): + user_profile = tmp_path / "Chrome" + user_profile.mkdir() + browser = ManualBrowser(run_id="testrun", prompt="grab jobs", output_dir=str(tmp_path / "runs")) + browser._playwright = MagicMock() + browser._playwright.chromium.launch_persistent_context.return_value.pages = [] + + with ( + patch("reverse_api.browser.get_chrome_profile_dir", return_value=user_profile), + patch("reverse_api.browser.console") as console, + patch.object(browser, "close", return_value=browser.har_path), + ): + browser._start_with_real_chrome() + + kwargs = browser._playwright.chromium.launch_persistent_context.call_args.kwargs + assert kwargs["channel"] == "chrome" + assert kwargs["user_data_dir"] != str(user_profile) + assert not any(user_profile.iterdir()) + printed = " ".join(str(call.args[0]) for call in console.print.call_args_list if call.args) + assert "fresh profile" in printed + assert "profile copy" not in printed diff --git a/website/content/docs/modes/manual.mdx b/website/content/docs/modes/manual.mdx index d1d6db8..6a42ba9 100644 --- a/website/content/docs/modes/manual.mdx +++ b/website/content/docs/modes/manual.mdx @@ -42,6 +42,8 @@ playwright install chromium Browse and interact with the website: click, search, scroll, paginate. + The browser starts with a fresh profile, without your existing logins, so + sign in as part of the capture if the site needs it. Close the browser when done.