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)
This commit is contained in:
parent
73882fac38
commit
ad394d38e7
4 changed files with 72 additions and 4 deletions
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -86,6 +89,15 @@ class CamoufoxManager:
|
|||
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:
|
||||
"""
|
||||
Extract cookies, storage, and fingerprint details into SessionState.
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
55
tests/unit/test_manager_integration.py
Normal file
55
tests/unit/test_manager_integration.py
Normal file
|
|
@ -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)
|
||||
Loading…
Reference in a new issue