From 232385f8d8033d025e640499f7d9ad48b108e04e Mon Sep 17 00:00:00 2001 From: protokey Date: Wed, 14 Oct 2026 16:54:40 +0400 Subject: [PATCH] =?UTF-8?q?fix:=20Chrome=20=D0=B1=D0=B5=D0=B7=20=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=B5=D0=B4=D0=B6=D0=B5=D1=80=D0=B0=20=D0=BF=D0=B0?= =?UTF-8?q?=D1=80=D0=BE=D0=BB=D0=B5=D0=B9,=20=D0=BF=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D0=B2=D0=BE=D0=B4=D0=B0=20=D0=B8=20=D0=B0=D0=B2=D1=82=D0=BE?= =?UTF-8?q?=D0=B7=D0=B0=D0=BF=D0=BE=D0=BB=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?(#111)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Перед каждым запуском окна в Default/Preferences профиля дописываются prefs: credentials_enable_service, profile.password_manager_enabled, translate.enabled, autofill.* = false. Применяется и к новым, и к существующим профилям, остальные настройки Chrome не трогаются. Пузырь «Сохранить пароль?» и предложение перевода больше не появляются, пароли не оседают в Login Data профиля. Co-Authored-By: Claude Opus 5.5 --- src/api/profile.py | 36 ++++++++++++++++++++++++++++++++++++ src/worker/windows.py | 2 ++ tests/unit/test_profile.py | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+) diff --git a/src/api/profile.py b/src/api/profile.py index 9c5cd87..4dcddb8 100644 --- a/src/api/profile.py +++ b/src/api/profile.py @@ -13,6 +13,7 @@ navigator.userAgentData и движком, и Cloudflare такой браузе """ import json import logging +import os import random from pathlib import Path @@ -24,6 +25,16 @@ SETTINGS_FILE = "robotex_session.json" # окно браузера под Xvfb — 1920x1080, монитор физически не может быть меньше окна MIN_SCREEN = (1920, 1080) CANVAS_NOISE_JS = (Path(__file__).parent / "js" / "canvas_noise.js").read_text() +# prefs Chrome, которые задаются профилю при каждом запуске (#111): без пузыря «Сохранить пароль?», +# предложения перевода и автозаполнения — секреты клиентов не оседают в Login Data/Web Data профиля. +# --disable-translate в современных Chrome не действует, поэтому через prefs. На JS-отпечаток не влияют. +CHROME_PREFS = { + "credentials_enable_service": False, + "credentials_enable_autosignin": False, + "profile": {"password_manager_enabled": False, "password_manager_leak_detection": False}, + "translate": {"enabled": False}, + "autofill": {"enabled": False, "profile_enabled": False, "credit_card_enabled": False}, +} def new_canvas_seed() -> int: @@ -81,3 +92,28 @@ def browser_kwargs(settings: dict) -> dict: "additional_args": {"screen": fp["screen"], "device_scale_factor": fp["device_scale_factor"]}, "init_script_content": CANVAS_NOISE_JS.replace("__SEED__", str(int(fp["canvas_seed"]))), } + + +def _merge(target: dict, source: dict) -> dict: + for key, value in source.items(): + if isinstance(value, dict) and isinstance(target.get(key), dict): + _merge(target[key], value) + else: + target[key] = value + return target + + +def apply_chrome_prefs(user_data_dir: Path) -> None: + """Дописать CHROME_PREFS в Default/Preferences профиля, не трогая остальное. Вызывается до запуска + Chrome: и для нового профиля, и для существующего — Chrome при выходе сохраняет эти prefs как есть.""" + path = user_data_dir / "Default" / "Preferences" + try: + prefs = json.loads(path.read_text()) + if not isinstance(prefs, dict): + prefs = {} + except (OSError, json.JSONDecodeError): + prefs = {} + path.parent.mkdir(parents=True, exist_ok=True) + tmp = path.with_name(path.name + ".robotex") + tmp.write_text(json.dumps(_merge(prefs, CHROME_PREFS), ensure_ascii=False)) + os.replace(tmp, path) diff --git a/src/worker/windows.py b/src/worker/windows.py index b86921d..c41d203 100644 --- a/src/worker/windows.py +++ b/src/worker/windows.py @@ -349,6 +349,8 @@ class WindowPool: touch_profile(user_data_dir) # отпечаток задаётся профилю один раз (см. api/profile.py) profile_kwargs = profile.browser_kwargs(profile.load_or_create(user_data_dir)) + # без менеджера паролей, перевода и автозаполнения (#111) + profile.apply_chrome_prefs(user_data_dir) proxy = parse_proxy(payload.proxy) if payload.proxy else None # geo — по exit-IP прокси (см. api/geoip.py); без прокси оставляем настройки браузера diff --git a/tests/unit/test_profile.py b/tests/unit/test_profile.py index 91ed946..bc62904 100644 --- a/tests/unit/test_profile.py +++ b/tests/unit/test_profile.py @@ -69,3 +69,39 @@ def test_profiles_get_different_canvas_seeds(tmp_path): (tmp_path / str(i)).mkdir() seeds.add(profile.load_or_create(tmp_path / str(i))["fingerprint"]["canvas_seed"]) assert len(seeds) == 20 + + +def test_chrome_prefs_for_new_profile(tmp_path): + profile.apply_chrome_prefs(tmp_path) + prefs = json.loads((tmp_path / "Default" / "Preferences").read_text()) + assert prefs["credentials_enable_service"] is False + assert prefs["profile"]["password_manager_enabled"] is False + assert prefs["translate"]["enabled"] is False + assert prefs["autofill"]["credit_card_enabled"] is False + + +def test_chrome_prefs_keep_existing_profile_settings(tmp_path): + """Существующий профиль: prefs дописываются, остальные настройки Chrome остаются.""" + path = tmp_path / "Default" / "Preferences" + path.parent.mkdir() + path.write_text(json.dumps({ + "profile": {"name": "Person 1", "password_manager_enabled": True}, + "translate": {"enabled": True}, + "autofill": {"autofill_ai": {"opt_in_status": {}}}, + "intl": {"accept_languages": "ru-RU,ru"}, + })) + profile.apply_chrome_prefs(tmp_path) + prefs = json.loads(path.read_text()) + assert prefs["profile"] == {"name": "Person 1", "password_manager_enabled": False, "password_manager_leak_detection": False} + assert prefs["translate"]["enabled"] is False + assert prefs["autofill"]["autofill_ai"] == {"opt_in_status": {}} + assert prefs["intl"] == {"accept_languages": "ru-RU,ru"} + assert prefs["credentials_enable_service"] is False + + +def test_chrome_prefs_replace_broken_preferences(tmp_path): + path = tmp_path / "Default" / "Preferences" + path.parent.mkdir() + path.write_text("{broken") + profile.apply_chrome_prefs(tmp_path) + assert json.loads(path.read_text())["translate"] == {"enabled": False}