feat: rewrite cloakserve as CDP multiplexer with per-connection fingerprint seeds

Spawns a separate Chrome process per unique fingerprint seed, all behind
a single port (9222). Clients specify seeds and fingerprint params via
query string on the CDP URL:

  connect_over_cdp("http://host:9222?fingerprint=12345&timezone=Asia/Tokyo")

Supports all --fingerprint-* flags as query params, geoip=true for
auto timezone/locale from proxy IP, and proxy= for per-process proxies.

- Rewrite bin/cloakserve from 57-line wrapper to aiohttp CDP multiplexer
- Add ChromePool with per-seed process management and port allocation
- Bidirectional WebSocket proxy for CDP traffic
- URL rewriting for /json/version, /json/list, and WS paths
- Rename _build_args -> build_args, _maybe_resolve_geoip -> maybe_resolve_geoip
- Add aiohttp + websockets to serve optional deps
- Dockerfile installs .[serve] extras
- Add 20 unit tests for cloakserve (param parsing, CLI args, URL rewriting)
This commit is contained in:
CloakHQ
2026-04-05 22:30:18 +02:00
committed by Durafen
parent c58b691f1c
commit 03a0ad732f
12 changed files with 795 additions and 87 deletions
+8 -8
View File
@@ -4,7 +4,7 @@ from unittest.mock import patch
import pytest
from cloakbrowser.browser import _maybe_resolve_geoip
from cloakbrowser.browser import maybe_resolve_geoip
from cloakbrowser.geoip import (
COUNTRY_LOCALE_MAP,
_is_private_ip,
@@ -92,25 +92,25 @@ def test_resolve_geo_returns_none_when_db_missing():
# ---------------------------------------------------------------------------
# _maybe_resolve_geoip (browser.py helper)
# maybe_resolve_geoip (browser.py helper)
# ---------------------------------------------------------------------------
def test_maybe_resolve_skips_when_geoip_false():
tz, loc = _maybe_resolve_geoip(False, "http://proxy:8080", None, None)
tz, loc = maybe_resolve_geoip(False, "http://proxy:8080", None, None)
assert tz is None
assert loc is None
def test_maybe_resolve_skips_when_no_proxy():
tz, loc = _maybe_resolve_geoip(True, None, None, None)
tz, loc = maybe_resolve_geoip(True, None, None, None)
assert tz is None
assert loc is None
def test_maybe_resolve_skips_when_both_explicit():
"""Explicit values should not trigger geoip resolution."""
tz, loc = _maybe_resolve_geoip(True, "http://proxy:8080", "Europe/Berlin", "de-DE")
tz, loc = maybe_resolve_geoip(True, "http://proxy:8080", "Europe/Berlin", "de-DE")
assert tz == "Europe/Berlin"
assert loc == "de-DE"
@@ -118,7 +118,7 @@ def test_maybe_resolve_skips_when_both_explicit():
def test_maybe_resolve_fills_missing_timezone():
"""When only locale is explicit, geoip should fill timezone."""
with patch("cloakbrowser.geoip.resolve_proxy_geo", return_value=("America/New_York", "en-US")):
tz, loc = _maybe_resolve_geoip(True, "http://proxy:8080", None, "fr-FR")
tz, loc = maybe_resolve_geoip(True, "http://proxy:8080", None, "fr-FR")
assert tz == "America/New_York"
assert loc == "fr-FR" # Explicit wins
@@ -126,7 +126,7 @@ def test_maybe_resolve_fills_missing_timezone():
def test_maybe_resolve_fills_missing_locale():
"""When only timezone is explicit, geoip should fill locale."""
with patch("cloakbrowser.geoip.resolve_proxy_geo", return_value=("America/New_York", "en-US")):
tz, loc = _maybe_resolve_geoip(True, "http://proxy:8080", "Asia/Tokyo", None)
tz, loc = maybe_resolve_geoip(True, "http://proxy:8080", "Asia/Tokyo", None)
assert tz == "Asia/Tokyo" # Explicit wins
assert loc == "en-US"
@@ -134,7 +134,7 @@ def test_maybe_resolve_fills_missing_locale():
def test_maybe_resolve_fills_both():
"""When neither is set, geoip should fill both."""
with patch("cloakbrowser.geoip.resolve_proxy_geo", return_value=("Europe/Berlin", "de-DE")):
tz, loc = _maybe_resolve_geoip(True, "http://proxy:8080", None, None)
tz, loc = maybe_resolve_geoip(True, "http://proxy:8080", None, None)
assert tz == "Europe/Berlin"
assert loc == "de-DE"