- scanner/engine.py — Playwright-движок сканирования - scanner/models.py — Pydantic модели - scanner/checks/ — 5 проверок (cookie, privacy, forms, server, external) - api/server.py — FastAPI REST API - reports/generator.py — PDF через WeasyPrint + Jinja2 - main.py — CLI + сервер Проверки: ✅ cookie_banner — поиск cookie-баннера ✅ privacy_link — проверка ссылки на политику ✅ form_checkbox — проверка чекбоксов согласия ✅ server_location — геолокация сервера (RФ или нет) ✅ external_scripts — внешние скрипты аналитики
111 lines
3.9 KiB
Python
111 lines
3.9 KiB
Python
"""
|
|
Проверка внешних скриптов, собирающих данные пользователей.
|
|
Google Analytics, Facebook Pixel, Яндекс.Метрика и другие —
|
|
отправляют данные на сервера за пределами РФ.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from playwright.async_api import Page
|
|
|
|
from scanner.checks.base import BaseCheck
|
|
from scanner.models import CheckResult, RiskLevel
|
|
|
|
|
|
class ExternalScriptsCheck(BaseCheck):
|
|
"""Проверяет наличие внешних скриптов, передающих данные за пределы РФ."""
|
|
|
|
name = "external_scripts"
|
|
title = "Внешние скрипты аналитики"
|
|
description = "Ищет скрипты Google Analytics, Facebook Pixel и другие иностранные сервисы"
|
|
risk_on_fail = RiskLevel.HIGH
|
|
default_weight = 15
|
|
|
|
EXTERNAL_PATTERNS = [
|
|
r"google-analytics\.com",
|
|
r"googletagmanager\.com",
|
|
r"googlesyndication\.com",
|
|
r"googleadservices\.com",
|
|
r"googleapis\.com",
|
|
r"google\.com/recaptcha",
|
|
r"facebook\.com/tr",
|
|
r"fbcdn\.net",
|
|
r"connect\.facebook\.net",
|
|
r"mc\.yandex\.ru",
|
|
r"monitoring\.yandex\.ru",
|
|
r"top\.mail\.ru",
|
|
r"clarity\.microsoft\.com",
|
|
r"hotjar\.com",
|
|
r"mixpanel\.com",
|
|
r"segment\.com",
|
|
r"amplitude\.com",
|
|
r"zendesk\.com",
|
|
r"intercom\.io",
|
|
r"chatra\.com",
|
|
r"tilda\.cdn",
|
|
r"wix\.com",
|
|
r"wordpress\.com",
|
|
r"shopify\.com",
|
|
]
|
|
|
|
async def check(self, page: Page, url: str) -> CheckResult:
|
|
try:
|
|
html = await page.evaluate("() => document.documentElement.outerHTML")
|
|
|
|
found_scripts = []
|
|
for pattern in self.EXTERNAL_PATTERNS:
|
|
matches = re.findall(pattern, html, re.IGNORECASE)
|
|
if matches:
|
|
found_scripts.append({
|
|
"pattern": pattern,
|
|
"matches": len(matches),
|
|
})
|
|
|
|
if not found_scripts:
|
|
return self._pass_result(
|
|
message="Внешние скрипты аналитики не найдены.",
|
|
)
|
|
|
|
critical_services = [
|
|
"google-analytics",
|
|
"googletagmanager",
|
|
"facebook",
|
|
"clarity",
|
|
"segment",
|
|
"amplitude",
|
|
]
|
|
|
|
has_critical = False
|
|
for script in found_scripts:
|
|
if any(re.search(c, script["pattern"]) for c in critical_services):
|
|
has_critical = True
|
|
break
|
|
|
|
if has_critical:
|
|
return self._fail_result(
|
|
message=(
|
|
f"Найдены внешние скрипты передачи данных: "
|
|
f"{', '.join(s['pattern'] for s in found_scripts)}. "
|
|
f"Они передают персональные данные за пределы РФ."
|
|
),
|
|
details={
|
|
"scripts_found": found_scripts,
|
|
"has_critical": True,
|
|
},
|
|
)
|
|
|
|
return self._fail_result(
|
|
message=(
|
|
f"Найдены внешние скрипты: "
|
|
f"{', '.join(s['pattern'] for s in found_scripts)}. "
|
|
f"Требуется согласие пользователя."
|
|
),
|
|
details={
|
|
"scripts_found": found_scripts,
|
|
"has_critical": False,
|
|
},
|
|
)
|
|
|
|
except Exception as e:
|
|
return self._error_result(message=f"Ошибка: {str(e)}")
|