🏗 Фаза 1-4: Архитектура, ядро, проверки, API, генератор отчетов

- 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 — внешние скрипты аналитики
This commit is contained in:
neo committed 2026-06-09 21:10:21 +05:00
commit de18090304
20 files changed
+1665

No files matched your search

+139
View File
@@ -0,0 +1,139 @@
"""
Проверка геолокации сервера.
Согласно 152-ФЗ (ст. 18), сбор персональных данных граждан РФ
должен производиться с использованием баз данных, находящихся на территории РФ.
"""
from __future__ import annotations
from playwright.async_api import Page
from scanner.checks.base import BaseCheck
from scanner.models import CheckResult, RiskLevel
class ServerLocationCheck(BaseCheck):
"""Проверяет, находится ли сервер сайта в России."""
name = "server_location"
title = "Геолокация сервера"
description = "Проверяет, находится ли сервер сайта на территории РФ"
risk_on_fail = RiskLevel.CRITICAL
default_weight = 25
# Популярные иностранные провайдеры (можно расширять)
RUSSIAN_HOSTERS = [
"yandex", "mail.ru", "vk", "selectel", "timeweb", "beget",
"reg.ru", "nic.ru", "rtcdn", "astra", "sbercloud",
"cloud4russia", "гостех", "ростелеком", "мтс", "билайн",
"мегафон", "вымпелком", "ростелеком", "netology",
]
async def check(self, page: Page, url: str) -> CheckResult:
try:
# Получаем IP-адрес сервера через JavaScript
ip_info = await page.evaluate("""() => {
return new Promise((resolve) => {
// Пробуем определить IP через несколько методов
const methods = [
() => fetch('https://api.ipify.org?format=json')
.then(r => r.json()).then(d => ({ip: d.ip, method: 'ipify'})),
() => fetch('https://api.my-ip.io/v2/json')
.then(r => r.json()).then(d => ({ip: d.ip, method: 'my-ip-io'})),
];
// Пробуем каждый метод
(async () => {
for (const method of methods) {
try {
const result = await method();
resolve(result);
return;
} catch (e) {}
}
resolve(null);
})();
});
}""")
if not ip_info:
return self._skip_result(
message="Не удалось определить IP-адрес сервера.",
)
ip = ip_info.get("ip", "")
method = ip_info.get("method", "")
# Определяем страну по IP (используем публичный API)
country = await self._get_country(ip)
if not country:
return self._skip_result(
message=f"Не удалось определить страну для IP {ip}.",
)
# Проверяем, Россия ли это
if country.lower() == "ru" or "Россия" in country:
return self._pass_result(
message=f"Сервер находится в России (IP: {ip}, Страна: {country})",
details={
"ip": ip,
"country": country,
"method": method,
},
)
# Сервер за рубежом — критическое нарушение
return self._fail_result(
message=(
f"Сервер находится ЗА ПРЕДЕЛАМИ РОССИИ ({country}, IP: {ip}). "
f"Это нарушение ст. 18 152-ФЗ: базы данных граждан РФ должны быть в РФ."
),
details={
"ip": ip,
"country": country,
"method": method,
},
)
except Exception as e:
return self._error_result(message=f"Ошибка при проверке геолокации: {str(e)}")
async def _get_country(self, ip: str) -> str | None:
"""Получает страну по IP через публичный API."""
try:
import urllib.request
import json
# Пробуем несколько бесплатных API
apis = [
f"https://ipapi.co/{ip}/json/",
f"https://ip-api.com/json/{ip}?fields=country",
f"https://api.ipgeolocation.io/ipgeo?ip={ip}",
]
for api_url in apis:
try:
req = urllib.request.Request(
api_url,
headers={"User-Agent": "Mozilla/5.0"}
)
with urllib.request.urlopen(req, timeout=5) as response:
data = json.loads(response.read().decode())
# ipapi.co формат
if "country_name" in data:
return data["country_name"]
# ip-api.com формат
if "country" in data:
return data["country"]
# ipgeolocation формат
if "country_name" in data:
return data["country_name"]
except Exception:
continue
return None
except Exception:
return None