Как технически проверить чужой сайт на 152-ФЗ за 30 секунд: архитектура сканера

Как технически проверить чужой сайт на 152-ФЗ за 30 секунд: архитектура сканера

В 2025 году штрафы по 152-ФЗ выросли с 60 тыс. до 18 млн ₽ (ч. 8 ст. 13.11 КоАП — повторная утечка ПДн объёмом 10+ млн записей). Параллельно РКН перешёл на массовые проверки сайтов: за 2024 год — 1 870 проверок и 1,2 млрд ₽ штрафов. Большинство нарушений — технические: нет HTTPS, нет cookie-баннера, форма без чекбокса согласия, политика в Google Docs.

Юристы умеют находить такие нарушения вручную за час. Мы написали сканер, который делает то же самое за 30 секунд. В статье — архитектура, scoring-подход к чекбоксам согласия, реальные грабли (политика в Google Docs, скрытые checkbox в Tilda, многошаговые формы записи в клиниках). Код на PHP 8, без зависимостей, ~1 800 строк.

Что вообще нужно от сканера 152-ФЗ

Закон 152-ФЗ задаёт оператору ПДн (любому, кто собирает имя/email/телефон через форму на сайте) 9 обязанностей. Технически с улицы можно проверить 8 из них:

HTTPS (ст. 19) — без TLS нельзя.

HTTPS (ст. 19) — без TLS нельзя.

Политика обработки ПДн на сайте оператора (ст. 18.1) — публичная, с 7 разделами.

Политика обработки ПДн на сайте оператора (ст. 18.1) — публичная, с 7 разделами.

Согласие на обработку ПДн в каждой форме (ст. 6 + 9) — отдельный, осознанный, не предустановленный checkbox.

Согласие на обработку ПДн в каждой форме (ст. 6 + 9) — отдельный, осознанный, не предустановленный checkbox.

Информирование об использовании cookie (ст. 16 Закона о связи + 152-ФЗ + практика РКН) — баннер с двумя кнопками.

Информирование об использовании cookie (ст. 16 Закона о связи + 152-ФЗ + практика РКН) — баннер с двумя кнопками.

Регистрация в реестре РКН (ст. 22) — если обработка не подпадает под исключения ч. 2.

Регистрация в реестре РКН (ст. 22) — если обработка не подпадает под исключения ч. 2.

Локализация БД (ч. 5 ст. 18) — БД ПДн россиян только в РФ.

Локализация БД (ч. 5 ст. 18) — БД ПДн россиян только в РФ.

Поручение обработки (ст. 6 ч. 3) — если используется внешний сервис (Tilda Forms, JivoSite, Bitrix24) — нужен договор.

Поручение обработки (ст. 6 ч. 3) — если используется внешний сервис (Tilda Forms, JivoSite, Bitrix24) — нужен договор.

Учёт ПДн сотрудников (ст. 10.1) — если на сайте есть страница «Команда» с ФИО/фото/должностями.

Учёт ПДн сотрудников (ст. 10.1) — если на сайте есть страница «Команда» с ФИО/фото/должностями.

Девятая — режим обработки внутри компании (приказ оператора, реестр субъектов, ответственный) — снаружи не виден.

Сканер делает все 8 проверок параллельно, агрегирует в один отчёт со ссылками на нарушенные нормы и потенциальный штраф.

Архитектура: разбор по слоям

Бэкенд — PHP 8.3 / nginx / PostgreSQL 16 на отдельной машине под api.imgchanger.org . Воркер запускается из обработчика handlers/p152_scan.php , библиотечная логика — в api/lib/p152_quick_scan.php .

Бюджет времени на один сайт — 5 секунд хард-кэп. Реальная медиана из прода — 2.1с (без кэша). При повторных сканированиях (TTL кэша — 7 дней) — мгновенно.

Главный трюк — curl_multi для всего

Наивная реализация качала бы кандидаты последовательно: главная (3с) → /privacy (3с) → /policy (3с) → /personal-data (3с) → … = 30+ секунд. Это тупик: пользователь уходит со страницы.

Решение — curl_multi_init + параллельная загрузка пачки URL:

Тонкости:

TIMEOUT 4с на URL — иначе один медленный сайт ломает весь батч.

TIMEOUT 4с на URL — иначе один медленный сайт ломает весь батч.

MAXREDIRS 3 — Tilda/Битрикс любят 301→301→301.

MAXREDIRS 3 — Tilda/Битрикс любят 301→301→301.

SSL_VERIFYPEER false — половина мелких сайтов с самоподписанными или просроченными сертами; мы не хотим падать на них.

SSL_VERIFYPEER false — половина мелких сайтов с самоподписанными или просроченными сертами; мы не хотим падать на них.

IDN-нормализация p152_idn_url() — для доменов .рф через idn_to_ascii() в xn-- , иначе libcurl не пройдёт.

IDN-нормализация p152_idn_url() — для доменов .рф через idn_to_ascii() в xn-- , иначе libcurl не пройдёт.

Это даёт честное ~4× ускорение: 15 URL загружаются за 3-4 секунды, а не за 15×3=45с.

Детектор форм — scoring вместо binary

Наивный подход: «есть <input type="checkbox"> рядом с формой → согласие есть». Это даёт ~30% false positive на реальном вебе. Потому что:

Битрикс рендерит чекбокс согласия в отдельном <div> вне <form> , в 400+ символах от формы (шаблоны личного кабинета).

Битрикс рендерит чекбокс согласия в отдельном <div> вне <form> , в 400+ символах от формы (шаблоны личного кабинета).

Tilda даёт checkbox с display:none и кладёт сверху красивый кастомный <label> — юридически это отсутствие чекбокса, пользователь не видит.

Tilda даёт checkbox с display:none и кладёт сверху красивый кастомный <label> — юридически это отсутствие чекбокса, пользователь не видит.

Caldera Forms (WordPress) даёт полям хэш-имена fld_154589 , а GDPR-чекбокс размечает через data-label="agreement" .

Caldera Forms (WordPress) даёт полям хэш-имена fld_154589 , а GDPR-чекбокс размечает через data-label="agreement" .

Многошаговые формы записи на приём в клиниках: первая страница — телефон, согласие — на третьем шаге в скрытом <div> за 6000 символов.

Многошаговые формы записи на приём в клиниках: первая страница — телефон, согласие — на третьем шаге в скрытом <div> за 6000 символов.

Решение — суммарный scoring по сигналам в окне ±400 символов вокруг <form> :

Видимый <input type=checkbox>

<input name=*acceptance/agreement/consent*> или data-label=agreement

Класс плагина: wpcf7-acceptance , t-checkbox , caldera-forms-consent-field

Кастомный лейбл <label class="checkbox/agree/consent">

Ссылка на политику в контексте формы ( href=*policy* )

Текст «согласие / 152-ФЗ / обработка персональных данных»

«Нажимая кнопку, я даю согласие…» + ссылка на политику

Многошаговый мастер: checkbox + consent-text в окне ±3000/6000

Решение: ≥6 → согласие есть; 3–5 → подозрительно (info, без штрафа); 0–2 → нарушение (high). Скрытый CSS-чекбокс отнимает 5 очков — формы только со скрытым чекбоксом помечаются checkbox_hidden_only .

В проде это даёт ~96% точности по нашему набору (340 размеченных вручную сайтов).

Детектор политики — приоритеты + контентный фильтр

Политику ищем в три захода:

Приоритет 1 — явно политика. URL содержит privacy/privacy-policy/politika-konfidencialn/personal-data , или текст ссылки матчит regex полит[а-я]*\s+(?:обработк|конфид|в\s+отношении\s+обраб) . Учитываем PDF-документы из медиабиблиотеки с тем же якорем — типовой паттерн Битрикс.

Приоритет 2 — вероятно политика. Менее уверенные URL/тексты.

Приоритет 3 — это согласие/оферта, не политика. URL содержит soglashenie/agreement/oferta/dogovor . Возвращается только как fallback с пометкой type='consent' .

После сбора кандидатов content-check : грузим страницу и в первых 30 000 символов текста ищем фразу «политика обработки» / «политика конфиденциальности» / «privacy policy». Если нет — это не политика, а что-то соседнее.

Это нужно потому что URL вроде /dokumenty/ или /info/ без анализа контента дают шум.

Свежая ловушка: политика в Google Docs

Реальный кейс из прода ( legalup.online , юридическая контора): на главной — три ссылки с правильными анкорами:

«Политика обработки персональных данных LegalUp» → docs.google.com/document/d/.../edit

«Политика обработки персональных данных LegalUp» → docs.google.com/document/d/.../edit

«Пользовательское соглашение LegalUp» → docs.google.com/document/d/.../edit

«Пользовательское соглашение LegalUp» → docs.google.com/document/d/.../edit

«Согласие на обработку персональных данных» → docs.google.com/document/d/.../edit

«Согласие на обработку персональных данных» → docs.google.com/document/d/.../edit

Регексп P1 ловит — кандидат отбирается. Дальше content-check грузит Google Docs /edit . И тут — тишина . Google Docs отдаёт каркас JS-приложения; текст документа подгружается через закрытый API только в браузере. В первых 30K символов HTML — никаких «политика обработки».

В первой версии сканер возвращал policy.found=false , как будто политики нет. Это технически неверно (политика есть, ссылка есть, документ открыть можно) и юридически тоже неполно — у этого паттерна свой риск.

Поправка: early-return до content-check, если хост кандидата — известный SaaS-хостинг:

Юридический смысл: политика в Google Docs (США) — это сразу ч. 5 ст. 18 (БД ПДн вне РФ — при открытии посетителем IP/cookies уходят в Google) + ст. 18.1 (политика должна быть на сайте оператора). Штраф по ч. 8 ст. 13.11 КоАП — до 6 млн ₽ при повторном нарушении локализации.

Внешние интеграции: реестр РКН + ГеоIP

Реестр РКН — pd.rkn.gov.ru/operators-registry . Поиск по ИНН → JSON со статусом регистрации. ИНН берём с сайта — из футера / реквизитов / политики через regex \b\d{10,12}\b + проверка контрольной суммы.

ГеоIP хостинга — ip-api.com (бесплатный лимит 45 req/min, для нас хватает). Отдаёт страну сервера, ASN, организацию. Если не РФ — пометка в отчёте: «БД на сайте может храниться вне РФ, проверьте ч. 5 ст. 18».

Эти два запроса делаем тоже параллельно с основным fetch главной — итоговый бюджет времени не растёт.

Антипаттерны, которые мы научились не путать

Поиск ≠ форма ПДн. Если в <form> ≤1 PII-поле + role="search" / action="/search" — это поиск, пропускаем.

Honeypot ≠ ПДн. Caldera/CF7 кладут невидимое поле name="web_site" для антиспама. Списком исключаем.

Регистрация ≠ Контактная форма. У формы регистрации часто бывают классы modal__search__input — search в substring наивно матчил бы их как поиск. Категории проверяем строго: role="search" , name="q" , name="s" , action="/search" .

checked в HTML ≠ pre-checked. Слово checked встречается в data-was-checked и в классах is-checked . Регексп — (?<![-\w])checked(?![-\w]) — только как boolean-атрибут.

Согласие действием. Битрикс/самописные сайты часто реализуют «Нажимая кнопку, я даю согласие на обработку…» + ссылка на политику. Юридически — серая зона: РКН в письмах рекомендует именно чекбокс, но прямого запрета нет. Для базовых форм без спецкатегорий ПДн — допустимо, ставим +3 в score.

Где сканер до сих пор слабоват

JS-rendered контент. Сканер на curl — видит только server-rendered HTML. Сайты на React/Vue/Next.js без SSR теряют 50-90% контента. План: добавить опциональный второй проход через headless-browser (chromium / playwright) для подозрительных сайтов.

JS-rendered контент. Сканер на curl — видит только server-rendered HTML. Сайты на React/Vue/Next.js без SSR теряют 50-90% контента. План: добавить опциональный второй проход через headless-browser (chromium / playwright) для подозрительных сайтов.

PDF-политика. Видим что это PDF, но не парсим. План: pdftotext для извлечения первых 5К символов и пропуск через p152_analyze_policy_sections .

PDF-политика. Видим что это PDF, но не парсим. План: pdftotext для извлечения первых 5К символов и пропуск через p152_analyze_policy_sections .

Подсчёт штрафа сейчас оценочный (mode по ч. 3 ст. 13.11 = 45 тыс. для юрлиц). Для реалистичной оценки — нужно учитывать оборот компании (для крупного бизнеса ч. 5 ст. 13.11 даёт до 18 млн), повторность, объём утечки. Это уже не «технический сканер», а юр.экспертиза.

Подсчёт штрафа сейчас оценочный (mode по ч. 3 ст. 13.11 = 45 тыс. для юрлиц). Для реалистичной оценки — нужно учитывать оборот компании (для крупного бизнеса ч. 5 ст. 13.11 даёт до 18 млн), повторность, объём утечки. Это уже не «технический сканер», а юр.экспертиза.

Что в итоге

Если оценивать «по делу»: технических нарушений 152-ФЗ на сайтах малого и среднего бизнеса в 2026 году — много. По нашим прогонам, 78% сайтов имеют хотя бы одно нарушение из категорий high; 31% — три и более.

Шесть месяцев работы сканера дали два неожиданных вывода:

Самое частое нарушение — не отсутствие политики , а скрытый CSS-чекбокс согласия в формах Tilda. Видимая «галка» — это псевдо-элемент :before , реальный <input> под display:none уже отмечен. Пользователь не может его снять.

Самое частое нарушение — не отсутствие политики , а скрытый CSS-чекбокс согласия в формах Tilda. Видимая «галка» — это псевдо-элемент :before , реальный <input> под display:none уже отмечен. Пользователь не может его снять.

Второе по частоте — политика не на своём домене : Google Docs, Notion, реже Dropbox. Авторы видят это как «галочка ради галочки», не понимая, что само по себе это два нарушения (локализация + публикация на сайте оператора).

Второе по частоте — политика не на своём домене : Google Docs, Notion, реже Dropbox. Авторы видят это как «галочка ради галочки», не понимая, что само по себе это два нарушения (локализация + публикация на сайте оператора).

← Cybersecurity