Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
10 changes: 7 additions & 3 deletions src/reverse_api/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
"""

Expand Down Expand Up @@ -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

Expand All @@ -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()
Expand Down
36 changes: 36 additions & 0 deletions tests/test_manual_browser_profile.py
Original file line number Diff line number Diff line change
@@ -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()
Comment on lines +17 to +18

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Profile copying goes undetected

The new test cannot detect copying the user's profile because user_profile is empty. Copying its contents into the separate launch directory still passes every assertion; an in-memory mutation confirmed this. This is a non-blocking coverage gap: a future change could copy existing logins while the test passes and the message still promises a fresh profile.

Add a synthetic cookie or settings file, assert that the source remains unchanged, and inspect user_data_dir inside the mocked launch call to check that the file was not copied. Check there because the temporary directory is deleted before the method returns.

Artifacts

Evidence from the check

  • The executed shell runner records the original pytest result and both runtime comparisons, making the validation reproducible.

Evidence from the check

  • The executed Python source injects copying only in memory and observes launch-directory contents, demonstrating the coverage gap without editing tracked files.

Command output from the check

  • The original targeted pytest test was executed without mutation and passed, establishing the baseline.

Command output from the check

  • The unchanged method was executed with empty and populated source profiles, showing an empty launch destination in both cases.

Command output from the check

  • The mutated method passed the original test and copied the populated fixture into the observed launch destination, confirming the missing destination-content check.

View artifacts

TREX Ran code and verified through TREX

Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/test_manual_browser_profile.py
Line: 17-18

Comment:
**Profile copying goes undetected**

The new test cannot detect copying the user's profile because `user_profile` is empty. Copying its contents into the separate launch directory still passes every assertion; an in-memory mutation confirmed this. This is a non-blocking coverage gap: a future change could copy existing logins while the test passes and the message still promises a fresh profile.

Add a synthetic cookie or settings file, assert that the source remains unchanged, and inspect `user_data_dir` inside the mocked launch call to check that the file was not copied. Check there because the temporary directory is deleted before the method returns.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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
2 changes: 2 additions & 0 deletions website/content/docs/modes/manual.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ playwright install chromium
</Step>
<Step>
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.
</Step>
<Step>
Close the browser when done.
Expand Down