From ad394d38e7dfc9f0a5e84ae51ce2ed14321642f4 Mon Sep 17 00:00:00 2001 From: Luciabrightcode Date: Tue, 23 Dec 2025 17:32:27 +0800 Subject: [PATCH] feat(evasion): release v1.0.1 hotfix (GhostCursor Integration) - Integrated GhostCursorEngine into CamoufoxManager.navigate() - Implemented random viewport targeting and micro-movements post-load - Added integration tests verifying call sequence - Fixed manual TLS verification script - Verified Scaling Limits (2.0 CPU) --- implementation_plan.md | 7 ++-- src/browser/manager.py | 12 ++++++ tests/manual/verify_tls.py | 2 +- tests/unit/test_manager_integration.py | 55 ++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 tests/unit/test_manager_integration.py diff --git a/implementation_plan.md b/implementation_plan.md index 75e905b..12d9400 100644 --- a/implementation_plan.md +++ b/implementation_plan.md @@ -44,6 +44,7 @@ Transition the system from a functional prototype to a scalable, production-read ## v1.0.1 Hotfix Plan - **Goal**: Enhance evasion by integrating GhostCursor into standard navigation and re-verifying constraints. - **Changes**: - - `src/browser/manager.py`: Integrate `GhostCursorEngine` for human-like movement during navigation. - - **Verification**: Run unit tests (`test_ghost_cursor`) and TLS checks (`verify_tls`). - - **Ops**: Verify `docker-compose.yml` CPU limits for Bezier overhead. + - `src/browser/manager.py`: Integrate `GhostCursorEngine` for human-like movement during navigation (**Completed**). + - **Verification**: Run unit tests (`test_ghost_cursor`) and TLS checks (`verify_tls`) (**Verified**). + - **Ops**: Verify `docker-compose.yml` CPU limits for Bezier overhead (**Verified**). + - **Governance**: **APPROVED** (Director: High Efficacy; Tech Lead: Arch Aligned). diff --git a/src/browser/manager.py b/src/browser/manager.py index d75a626..7e77fe9 100644 --- a/src/browser/manager.py +++ b/src/browser/manager.py @@ -3,6 +3,8 @@ import logging from typing import Optional, Dict, Any from playwright.async_api import async_playwright, BrowserContext, Page, Browser from src.core.session import SessionState +from src.browser.ghost_cursor import GhostCursorEngine +import random # Configure logging logging.basicConfig( @@ -25,6 +27,7 @@ class CamoufoxManager: self.page: Optional[Page] = None # Updated to Chrome 124 to align with newer Playwright builds and curl_cffi support self._dummy_user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" + self.ghost_cursor = GhostCursorEngine() async def __aenter__(self): await self.initialize() @@ -85,6 +88,15 @@ class CamoufoxManager: raise RuntimeError("Browser not initialized") logger.info(f"Navigating to {url}") await self.page.goto(url, wait_until='domcontentloaded') + + # v1.0.1 Hotfix: Human Mimesis + # Move mouse to a random position within viewport (1920x1080) + target_x = random.randint(100, 1800) + target_y = random.randint(100, 900) + await self.ghost_cursor.move_to(self.page, target_x, target_y) + + # Simulate reading/fidgeting + await self.ghost_cursor.random_micro_movement(self.page) async def extract_session_state(self) -> SessionState: """ diff --git a/tests/manual/verify_tls.py b/tests/manual/verify_tls.py index 50cd820..cb4ca4f 100644 --- a/tests/manual/verify_tls.py +++ b/tests/manual/verify_tls.py @@ -78,7 +78,7 @@ async def verify_tls(): # Use the session state from browser to ensure same UA/headers context async with CurlClient(session_state) as client: response = await client.fetch(TARGET_URL) - data = response.json() + data = json.loads(response) client_ja3 = data.get('ja3_hash', 'UNKNOWN') client_ua = data.get('user_agent', 'UNKNOWN') diff --git a/tests/unit/test_manager_integration.py b/tests/unit/test_manager_integration.py new file mode 100644 index 0000000..86b8626 --- /dev/null +++ b/tests/unit/test_manager_integration.py @@ -0,0 +1,55 @@ +import pytest +from unittest.mock import AsyncMock, patch +from src.browser.manager import CamoufoxManager + +@pytest.mark.asyncio +async def test_manager_integration_ghost_cursor(): + """ + Verify that CamoufoxManager.navigate calls GhostCursor methods. + """ + # Mock GhostCursorEngine within the manager module context if possible, + # or better, mock it via patch on the class instance after init if easier, + # but since it's instantiated in __init__, we patch the class in the module. + + with patch('src.browser.manager.GhostCursorEngine') as MockGhostEngine, \ + patch('src.browser.manager.async_playwright') as MockPlaywright: + + # Setup Mocks + mock_engine_instance = MockGhostEngine.return_value + mock_engine_instance.move_to = AsyncMock() + mock_engine_instance.random_micro_movement = AsyncMock() + + mock_pw_context = AsyncMock() + MockPlaywright.return_value.start.return_value = mock_pw_context + # Mock browser, context, page chain... + # CamoufoxManager.initialize is complex to mock fully without heavy boilerplate. + # We can try to rely on the fact that navigate checks self.page. + + # Let's instantiate manager + manager = CamoufoxManager() + + # Manually set the mock page to bypass initialize() complexity if we just want to test navigate logic + mock_page = AsyncMock() + manager.page = mock_page + + # We also need to inject our mock engine instance if the init already ran? + # Yes, line `self.ghost_cursor = GhostCursorEngine()` ran during init. + # So manager.ghost_cursor is the mock_engine_instance provided by patch. + + # EXECUTE + await manager.navigate("http://example.com") + + # VERIFY + # 1. Page goto called + mock_page.goto.assert_awaited_once_with("http://example.com", wait_until='domcontentloaded') + + # 2. GhostCursor move_to called + assert mock_engine_instance.move_to.called + args = mock_engine_instance.move_to.call_args[0] + # args: (page, x, y) + assert args[0] == mock_page + assert 100 <= args[1] <= 1800 # x check + assert 100 <= args[2] <= 900 # y check + + # 3. Random micro movement called + mock_engine_instance.random_micro_movement.assert_awaited_once_with(mock_page)