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:
Luciabrightcode 2025-12-23 17:32:27 +08:00
parent 73882fac38
commit ad394d38e7
4 changed files with 72 additions and 4 deletions

View file

@ -44,6 +44,7 @@ Transition the system from a functional prototype to a scalable, production-read
## v1.0.1 Hotfix Plan ## v1.0.1 Hotfix Plan
- **Goal**: Enhance evasion by integrating GhostCursor into standard navigation and re-verifying constraints. - **Goal**: Enhance evasion by integrating GhostCursor into standard navigation and re-verifying constraints.
- **Changes**: - **Changes**:
- `src/browser/manager.py`: Integrate `GhostCursorEngine` for human-like movement during navigation. - `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`). - **Verification**: Run unit tests (`test_ghost_cursor`) and TLS checks (`verify_tls`) (**Verified**).
- **Ops**: Verify `docker-compose.yml` CPU limits for Bezier overhead. - **Ops**: Verify `docker-compose.yml` CPU limits for Bezier overhead (**Verified**).
- **Governance**: **APPROVED** (Director: High Efficacy; Tech Lead: Arch Aligned).

View file

@ -3,6 +3,8 @@ import logging
from typing import Optional, Dict, Any from typing import Optional, Dict, Any
from playwright.async_api import async_playwright, BrowserContext, Page, Browser from playwright.async_api import async_playwright, BrowserContext, Page, Browser
from src.core.session import SessionState from src.core.session import SessionState
from src.browser.ghost_cursor import GhostCursorEngine
import random
# Configure logging # Configure logging
logging.basicConfig( logging.basicConfig(
@ -25,6 +27,7 @@ class CamoufoxManager:
self.page: Optional[Page] = None self.page: Optional[Page] = None
# Updated to Chrome 124 to align with newer Playwright builds and curl_cffi support # 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._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): async def __aenter__(self):
await self.initialize() await self.initialize()
@ -85,6 +88,15 @@ class CamoufoxManager:
raise RuntimeError("Browser not initialized") raise RuntimeError("Browser not initialized")
logger.info(f"Navigating to {url}") logger.info(f"Navigating to {url}")
await self.page.goto(url, wait_until='domcontentloaded') 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: async def extract_session_state(self) -> SessionState:
""" """

View file

@ -78,7 +78,7 @@ async def verify_tls():
# Use the session state from browser to ensure same UA/headers context # Use the session state from browser to ensure same UA/headers context
async with CurlClient(session_state) as client: async with CurlClient(session_state) as client:
response = await client.fetch(TARGET_URL) response = await client.fetch(TARGET_URL)
data = response.json() data = json.loads(response)
client_ja3 = data.get('ja3_hash', 'UNKNOWN') client_ja3 = data.get('ja3_hash', 'UNKNOWN')
client_ua = data.get('user_agent', 'UNKNOWN') client_ua = data.get('user_agent', 'UNKNOWN')

View 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)