Files
neo b9db935389 v2.0: исправлены критические баги, добавлены 4 новые проверки, 4-фазный cookie-аудит, retry-логика, YAML-словари, LLM-анализ, тесты
Исправлено:
- engine: _calculate_score() при ошибке загрузки (score не 100 для мёртвых сайтов)
- privacy_link: замена page.goto() на aiohttp (гонка данных)
- form_checkbox: 3-уровневый поиск label + dark-pattern detection
- server_location: синхронный urllib → aiohttp
- api/server: перепутанные request/response модели в batch
- main.py: csv --checks флаг
- engine: фазовая модель (cookie_audit отдельно, остальные параллельно)
- engine: retry-логика (3 попытки, exponential backoff)
- PDF: emoji заменены на текст (WeasyPrint)

Добавлено:
- consent_doc: проверка отдельного согласия на обработку ПД
- operator_info: проверка ИНН/ОГРН/DPO (читает главную + политику)
- cookie_audit: 4-фазный аудит (load→detect→reject→compare)
- llm_policy: опциональный LLM-анализ 16 разделов (--deep)
- external_scripts: YAML-словари trackers.yaml (critical/warning/russian)
- logging (output.py), .gitignore, pyproject.toml
- smoke-тесты (16/16)
2026-06-10 00:49:07 +05:00

115 lines
4.7 KiB
Python

"""
Проверка геолокации сервера.
Согласно 152-ФЗ (ст. 18), сбор персональных данных граждан РФ
должен производиться с использованием баз данных, находящихся на территории РФ.
"""
from __future__ import annotations
import socket
from urllib.parse import urlparse
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
# Популярные русские DNS-серверы/хостеры
RUSSIAN_HOSTERS = [
"yandex", "mail.ru", "vk", "selectel", "timeweb", "beget",
"reg.ru", "nic.ru", "rtcdn", "astra", "sbercloud",
"cloud4russia", "гостех", "ростелеком", "мтс", "билайн",
"мегафон", "вымпелком", "netology",
"yandex.net", "mail.ru", "vk.com", "selectel.cloud",
]
async def check(self, page: Page, url: str) -> CheckResult:
try:
# Извлекаем домен из URL
parsed = urlparse(url)
hostname = parsed.hostname or ""
if not hostname:
return self._skip_result(message="Не удалось извлечь домен из URL.")
# Получаем IP через DNS
try:
ip = socket.gethostbyname(hostname)
except socket.gaierror:
return self._skip_result(message=f"Не удалось разрешить DNS для {hostname}.")
# Определяем страну по IP
country = await self._get_country(ip)
if not country:
return self._skip_result(message=f"Не удалось определить страну для IP {ip}.")
# Проверяем, Россия ли это
if country.lower() == "ru" or "Россия" in country or "russia" in country.lower():
return self._pass_result(
message=f"Сервер находится в России (IP: {ip}, Страна: {country})",
details={
"ip": ip,
"country": country,
},
)
# Сервер за рубежом — критическое нарушение
return self._fail_result(
message=(
f"Сервер находится ЗА ПРЕДЕЛАМИ РОССИИ ({country}, IP: {ip}). "
f"Это нарушение ст. 18 152-ФЗ: базы данных граждан РФ должны быть в РФ."
),
details={
"ip": ip,
"country": country,
},
)
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 aiohttp
apis = [
f"https://ipapi.co/{ip}/json/",
f"https://ip-api.com/json/{ip}?fields=country",
]
async with aiohttp.ClientSession() as session:
for api_url in apis:
try:
async with session.get(
api_url,
headers={"User-Agent": "Mozilla/5.0"},
timeout=aiohttp.ClientTimeout(total=5),
) as response:
if response.status != 200:
continue
data = await response.json()
# ipapi.co формат
if "country_name" in data:
return data["country_name"]
# ip-api.com формат
if "country" in data:
return data["country"]
except Exception:
continue
return None
except Exception:
return None