""" Проверка внешних скриптов, собирающих данные пользователей. Загружает словари трекеров из dictionaries/trackers.yaml. Разделяет на категории: CRITICAL (запрещены), WARNING (рискованны), RUSSIAN (допустимы). """ from __future__ import annotations import re from pathlib import Path import yaml from playwright.async_api import Page from scanner.checks.base import BaseCheck from scanner.models import CheckResult, RiskLevel # Путь к словарю трекеров TRACKERS_YAML = Path(__file__).parent.parent.parent / "dictionaries" / "trackers.yaml" class ExternalScriptsCheck(BaseCheck): """Проверяет наличие внешних скриптов, передающих данные за пределы РФ.""" name = "external_scripts" title = "Внешние скрипты аналитики" description = "Ищет скрипты Google Analytics, Meta Pixel и другие иностранные сервисы" risk_on_fail = RiskLevel.HIGH default_weight = 15 def _load_trackers(self) -> dict: """Загрузить словарь трекеров из YAML.""" if not TRACKERS_YAML.exists(): # Fallback: базовый набор return { "critical": [{"domain": "google-analytics.com"}, {"domain": "facebook.com/tr"}], "warning": [{"domain": "fonts.googleapis.com"}, {"domain": "google.com/recaptcha"}], "russian": [{"domain": "mc.yandex.ru"}, {"domain": "vk.com/rtrg"}], } with open(TRACKERS_YAML, "r", encoding="utf-8") as f: return yaml.safe_load(f) async def check(self, page: Page, url: str) -> CheckResult: try: html = await page.evaluate("() => document.documentElement.outerHTML") trackers = self._load_trackers() found = {"critical": [], "warning": [], "russian": []} for category in ("critical", "warning", "russian"): for entry in trackers.get(category, []): domain = entry["domain"] if re.search(domain, html, re.IGNORECASE): found[category].append(entry) if not any(found.values()): return self._pass_result( message="Внешние скрипты аналитики не найдены.", ) # Формируем сообщение parts = [] if found["critical"]: domains = [e["domain"] for e in found["critical"]] parts.append(f"ЗАПРЕЩЁННЫЕ ({len(found['critical'])}): {', '.join(domains)}") if found["warning"]: domains = [e["domain"] for e in found["warning"]] parts.append(f"РИСКОВАННЫЕ ({len(found['warning'])}): {', '.join(domains)}") if found["russian"]: domains = [e["domain"] for e in found["russian"]] parts.append(f"Российские ({len(found['russian'])}): {', '.join(domains)}") message = "Найдены внешние скрипты: " + " | ".join(parts) # Только российские → PASS с уведомлением if not found["critical"] and not found["warning"]: return self._pass_result( message=f"Найдены только российские сервисы: {message}", details={"found": found}, ) # Есть критические → FAIL if found["critical"]: actions = [f"{e['domain']}: {e.get('action', 'Удалить')}" for e in found["critical"]] message += f"\nРекомендации: {'; '.join(actions)}" return self._fail_result( message=message, risk_level=RiskLevel.CRITICAL, details={"found": found}, ) # Только warning → FAIL с HIGH return self._fail_result( message=message, risk_level=RiskLevel.HIGH, details={"found": found}, ) except Exception as e: return self._error_result(message=f"Ошибка при проверке внешних скриптов: {str(e)}")