From c871f804cf089b14822d519d1a19ee27b6190cec Mon Sep 17 00:00:00 2001 From: bot-backend Date: Thu, 17 Sep 2026 12:49:37 +0500 Subject: [PATCH] =?UTF-8?q?=D0=A1=D0=B0=D0=B9=D0=B4=D0=BA=D0=B0=D1=80:=20?= =?UTF-8?q?=D1=82=D0=B0=D0=B9=D0=BC=D0=B7=D0=BE=D0=BD=D0=B0=20=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D1=83=D0=B7=D0=B5=D1=80=D0=B0=20=D0=B1=D0=B5=D1=80=D1=91?= =?UTF-8?q?=D1=82=D1=81=D1=8F=20=D0=B8=D0=B7=20IP=20=D1=83=D0=B7=D0=BB?= =?UTF-8?q?=D0=B0,=20=D0=B0=20=D0=BD=D0=B5=20=D0=B6=D1=91=D1=81=D1=82?= =?UTF-8?q?=D0=BA=D0=BE=20=D0=9C=D0=A1=D0=9A=20(#3187)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit camoufox 0.5.5 при geoip=True кладёт координаты из GeoIP-записи exit-IP безусловно, а timezone — через setdefault, поэтому ручной config["timezone"]="Europe/Moscow" перебивал зону из IP. Узлы пула 13 и 15 (T2 NSK, UTC+7) и 14 (МегаФон Самара, UTC+4, RIPE 17.09) заявляли МСК при сибирских/самарских координатах, а библиотека писала LeakWarning на каждом запуске (4715 штук за 13 ч при 665 запусках). Ротация узла идёт внутри подсети оператора, смена узла релончит браузер — зона с запуска не протухает. Тест: в kwargs запуска нет ключей timezone/geolocation:* в config, geoip=True. Co-Authored-By: Claude Opus 5 --- tradein-mvp/browser/server.py | 14 ++++-- .../browser/test_server_proxy_override.py | 43 +++++++++++++++++++ 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/tradein-mvp/browser/server.py b/tradein-mvp/browser/server.py index 71de6501..1b9d98ea 100644 --- a/tradein-mvp/browser/server.py +++ b/tradein-mvp/browser/server.py @@ -895,10 +895,16 @@ async def _launch_browser(provider: str, proxy_override: str | None = None) -> N "block_images": _BLOCK_IMAGES_BY_PROVIDER.get(provider, _BLOCK_IMAGES_DEFAULT_FALLBACK), # Анти-leak: WebRTC может слить реальный IP контейнера мимо прокси → деанон. "block_webrtc": True, - # Форсим таймзону +3 МСК. geoip берёт tz одного IP на старте, а БэкКоннект - # ротирует IP на каждый запрос (видели Ульяновск UTC+4 при выходе СПб/МСК) → - # геолик tz↔IP. Жёсткий Europe/Moscow убирает рассинхрон для РФ-мобильного. - "config": {"timezone": "Europe/Moscow"}, + # Таймзону НЕ задаём (#3187): её ставит geoip=True по exit-IP на запуске — + # из той же GeoIP-записи, что и координаты geolocation. Жёсткий + # Europe/Moscow в config (было) camoufox 0.5.5 применял поверх geoip + # (timezone — setdefault), а координаты брал из IP: для узлов вне UTC+3 + # браузер заявлял МСК при новосибирских координатах и адресе (узлы 13/15 — + # T2 NSK, UTC+7; 14 — МегаФон Самара, UTC+4 по RIPE, 17.09.2026) и писал + # LeakWarning «Please use the geoip parameter» на каждом запуске. Ротация + # узла пула меняет IP внутри той же подсети оператора (health-логи 48 ч: + # 176.59.137.x/176.59.144.x у 15, 178.176.78-79.x у 14), а смена узла + # релончит браузер (_launched_proxy) — зона, взятая на запуске, не протухает. } if proxy is not None: kwargs["proxy"] = proxy diff --git a/tradein-mvp/browser/test_server_proxy_override.py b/tradein-mvp/browser/test_server_proxy_override.py index 2ec4fdd4..dd124b6b 100644 --- a/tradein-mvp/browser/test_server_proxy_override.py +++ b/tradein-mvp/browser/test_server_proxy_override.py @@ -149,3 +149,46 @@ def test_ensure_browser_env_then_pool_relaunches(monkeypatch: pytest.MonkeyPatch asyncio.run(_run()) assert calls == [("avito", None), ("avito", "http://pool:8080")] + + +# ── #3187: гео-параметры запуска camoufox ──────────────────────────────────────── + + +def test_launch_leaves_timezone_to_geoip(monkeypatch: pytest.MonkeyPatch) -> None: + """Зона и координаты — только от geoip по exit-IP, вручную в config не задаются. + + camoufox 0.5.5 (utils.launch_options): geoip кладёт в config координаты + безусловно, а timezone — через setdefault, то есть ручной config["timezone"] + перебивает зону из IP, и координаты с зоной расходятся (узел в Новосибирске + с заявленной МСК). Ключи 'timezone' и 'geolocation:*' в config — ровно те, + на которые библиотека пишет LeakWarning (warn_manual_config). + """ + import sys + import types + + captured: dict[str, object] = {} + + class _FakeCamoufox: + def __init__(self, **kwargs: object) -> None: + captured.update(kwargs) + + async def __aenter__(self) -> object: + return object() + + fake_mod = types.ModuleType("camoufox.async_api") + fake_mod.AsyncCamoufox = _FakeCamoufox # type: ignore[attr-defined] + monkeypatch.setitem(sys.modules, "camoufox", types.ModuleType("camoufox")) + monkeypatch.setitem(sys.modules, "camoufox.async_api", fake_mod) + + asyncio.run(server._launch_browser("avito", proxy_override="http://u:p@176.59.137.239:8080")) + + assert captured["geoip"] is True + assert captured["proxy"] == { + "server": "http://176.59.137.239:8080", + "username": "u", + "password": "p", + } + config = captured.get("config", {}) + assert isinstance(config, dict) + manual_geo = sorted(k for k in config if k == "timezone" or k.startswith("geolocation:")) + assert manual_geo == []