Перейти к содержанию

Точность проверок

Как проверялось, что Suveren не врёт. Три источника, от синтетического к живому:

  1. Полигон (tests/lab.py): 160 синтетических сайтов с заранее известными проблемами. Ответ для каждого случая записан по стандарту или закону (RFC 7208, 7489, 8301, 8461, 8659, 8624, 8996, 6797, 4035, OWASP Secure Headers, 152-ФЗ) до запуска Suveren. Случай прогоняется через тот же код, что и suveren scan, без сети: страницы отдаёт подставной HTTP-транспорт, DNS — подставной резолвер, TLS — заданный результат. Тест tests/test_lab.py следит, чтобы ответы не поменялись.
  2. Стенды badssl.com для проверки TLS (docs/standards-review.md).
  3. Размеченная выборка реальных сайтов (tools/accuracy.py, как собрать — в docs/accuracy-sample.md): точность и полнота с 95% доверительными интервалами. Выборку размечает человек, глядя на сайт.
  4. Реальные сайты: golden-корпус из 20 сайтов (тесты) и ночной прогон по 300 сайтам. Там не известен правильный ответ заранее, зато видны ложные тревоги на настоящей вёрстке.

Пересобрать таблицу: python tests/lab.py --markdown.

Полигон, 2026-11-09

«Проблема» — статус «внимание» или «проблема». Полнота — какую долю настоящих проблем проверка нашла; точность — какая доля её тревог настоящая.

Проверка Нашла проблем Пропустила Ложных тревог Верно «в порядке» Полнота Точность
SUV-LAW-002 1 0 0 2 100% 100%
SUV-LAW-003 0 0 0 2 — —
SUV-LAW-004 1 0 0 2 100% 100%
SUV-MAIL-001 6 0 0 4 100% 100%
SUV-MAIL-002 7 0 0 4 100% 100%
SUV-MAIL-003 6 0 0 4 100% 100%
SUV-MAIL-004 0 0 0 7 — —
SUV-MAIL-005 4 0 0 3 100% 100%
SUV-MAIL-006 3 0 0 5 100% 100%
SUV-MAIL-007 0 0 0 3 — —
SUV-OWN-001 2 0 0 2 100% 100%
SUV-PD-001 2 0 0 2 100% 100%
SUV-PD-002 1 0 0 2 100% 100%
SUV-PD-003 5 0 0 5 100% 100%
SUV-PD-004 2 0 0 0 100% 100%
SUV-PD-005 3 0 0 1 100% 100%
SUV-PD-006 2 0 0 2 100% 100%
SUV-PD-007 4 0 0 3 100% 100%
SUV-PD-008 2 0 0 2 100% 100%
SUV-PD-009 0 0 0 1 — —
SUV-PD-011 6 0 0 3 100% 100%
SUV-SUB-003 1 0 0 4 100% 100%
SUV-SUB-004 1 0 0 1 100% 100%
SUV-WEB-003 1 0 0 1 100% 100%
SUV-WEB-004 5 0 0 5 100% 100%
SUV-WEB-005 4 0 0 1 100% 100%
SUV-WEB-006 4 0 0 5 100% 100%
SUV-WEB-007 2 0 0 3 100% 100%
SUV-WEB-008 6 0 0 4 100% 100%
SUV-WEB-009 4 0 0 3 100% 100%
SUV-WEB-010 8 0 0 5 100% 100%
SUV-WEB-012 2 0 0 3 100% 100%
SUV-WEB-013 3 0 0 4 100% 100%
SUV-WEB-014 1 0 0 2 100% 100%
SUV-WEB-015 2 0 0 2 100% 100%
SUV-WEB-016 0 0 0 4 — —
SUV-WEB-017 2 0 0 2 100% 100%

SUV-MAIL-004 (DKIM) не выдаёт «проблему»: ключ нельзя перечислить, можно только угадать селектор, поэтому отсутствие ключа — всегда «не ясно». На полигоне четыре случая (с 2026-10-23): ключ под известным селектором и под редким (оба «в порядке», второй — по узлу _domainkey), пустой _domainkey и зона с *-записью (оба «не ясно»). В таблице «не ясно» засчитано как верное «не проблема».

SUV-LAW-003 (маркировка рекламы) с 2.1 при найденных erid говорит «не ясно», а не «в порядке»: видимый токен не доказывает, что промаркирована вся реклама, что рядом есть пометка «Реклама» с рекламодателем и что токен зарегистрирован в ЕРИР. Без erid проверки нет.

SUV-LAW-004 (Instagram и Facebook без пометки о Meta) — только «внимание»: ссылка на instagram.com, facebook.com, fb.me есть, а на той же странице нет пометки о том, что Meta признана экстремистской и запрещена в РФ. Пометка засчитывается, только если рядом с названием стоит запрет именно в России («экстремистск…», «запрещена в РФ/России»), чтобы «копирование запрещено» не сошло за пометку. Без ссылок проверки нет.

SUV-WEB-008 (TLS) с 1.1.3 говорит «в порядке» только когда проверено всё, что она проверяет: доверие браузеров к сертификату и TLS 1.0/1.1. Если проверяющее рукопожатие не ответило или про старые версии TLS узнать не удалось — «не ясно» (случаи lab-tls-unverified, lab-tls-legacy-unknown). Раньше там было «в порядке».

SUV-MAIL-006 (длина ключа DKIM) сверена с RFC 8301 (разделы 3.1–3.2): RSA короче 1024 бит и ключ только для SHA-1 — «проблема» (получатели обязаны не принимать такие подписи), 1024–2047 бит — «внимание» (стандарт советует 2048), 2048 и больше и Ed25519 (RFC 8463) — «в порядке», нечитаемый p= — «не ясно». Ключ на полигоне собирается с нужной длиной модуля, в том числе в виде голого RSAPublicKey. Если ни один селектор не угадан, проверка не выдаётся: длину мерить не у чего.

SUV-WEB-012 (CAA) и SUV-WEB-013 (DNSSEC) смотрят только DNS. CAA по RFC 8659: записи нет ни у имени, ни у родителей — «в порядке» с пояснением (стандарт CAA не требует), тег с флагом critical, которого центры не знают, и issue ";" у сайта с HTTPS — «внимание» (выпуск сертификата сорвётся). DNSSEC по RFC 4035 (раздел 5) и RFC 8624: зона без DS — «в порядке» с пояснением, DS без ключей или не подходящая ни к одному ключу — «проблема» (проверяющие резолверы не откроют сайт), подпись только устаревшим алгоритмом — «внимание». Подписи RRSIG и их сроки не проверяются: резолверы их не отдают.

SUV-SUB-003 (висящие A-записи) никогда не говорит «проблема»: по DNS и молчанию сервера нельзя доказать, что адрес достался другому. «Внимание» — когда поддомен A-записью указывает на адрес из опубликованных списков AWS (только EC2), Google Cloud, Oracle Cloud или DigitalOcean и ни HTTPS, ни HTTP на нём не отвечают. Отвечающий сервер, свой (не облачный) адрес и общие фронты вроде CloudFront — «в порядке»; списки не скачались — «не ясно». На полигоне списки облаков урезаны до нескольких сетей в настоящем формате.

SUV-WEB-010 (уязвимые библиотеки) с 1.2 сверяет версии с базой retire.js, а не со своим списком. Полигон работает без сети, поэтому на нём действует встроенная копия базы (дата — в доказательствах). Четыре новых случая показывают, где ответ поменялся: Bootstrap 3.4.1 и lodash 4.17.21 теперь «внимание» (CVE-2024-6485, CVE-2025-13465 и конец поддержки ветки 3.x), AngularJS 1.8.2 — «внимание» с номерами CVE, moment.js 2.29.4 — «в порядке»: его CVE-2026-17495 бывает только на сервере.

В 2.1 SUV-WEB-010 узнаёт Vue.js и React (по пути к файлу и по заголовку в начале файла). Четыре случая: Vue 2.6.14 — «внимание» (CVE-2024-9506), Vue 3.4.21 — «в порядке», React 0.13.3 — «внимание» (GHSA-hg79-j56m-fxgv), React 16.4.1 — «в порядке»: его CVE-2018-6341 касается только отрисовки на сервере (react-dom/server), как и записи retire.js о серверной отрисовке Vue (#10, #11). Похожие пакеты — @vue/runtime-core, vue-router, preact, scheduler со строкой лицензии React — библиотекой не считаются.

SUV-WEB-016 (версия CMS, 2.1) нарочно отвечает только «не ясно»: открытой базы уязвимостей по версиям WordPress и 1С-Битрикс без ключа API нет, поэтому полнота и точность в таблице — прочерки. Случаи проверяют, что CMS узнаётся и не выдумывается: WordPress 6.4.3 по тегу generator и wp-includes, 1С-Битрикс по путям /bitrix/ (версия не видна), а картинка с чужого блога на WordPress и ссылка «Сайт на 1С-Битрикс» проверки не дают.

SUV-WEB-017 (Permissions-Policy, W3C; OWASP Secure Headers советует его ставить): заголовка нет — «внимание» с низкой срочностью; заголовок в синтаксисе Feature-Policy (camera 'none') не разбирается как структурированное поле (RFC 8941) и браузером отбрасывается — тоже «внимание»; camera=() или geolocation=(self ...) — «в порядке». В эталонный случай lab-good заголовок добавлен.

SUV-MAIL-007 (BIMI) — только для сведения и всегда «в порядке»: на защиту от подделки запись не влияет. Три случая проверяют, что ответ не превращается в предупреждение: записи нет (lab-good), запись с логотипом при DMARC p=reject, запись при DMARC p=none (в тексте сказано, что логотип не покажут).

SUV-MAIL-002 в 2.1: если DNS не ответил на include или redirect, запрос повторяется один раз. В ночных прогонах один потерянный ответ переключал проверку между «в порядке» и «внимание». Случай lab-spf-include-hiccup (ответ со второго раза) до исправления давал «внимание», теперь «в порядке»; lab-spf-include-silent (не ответил и при повторе) — по-прежнему «внимание».

Веб-проверки 1.2. SUV-WEB-014 (HTTP→HTTPS, OWASP TLS Cheat Sheet): сайт по http:// отдаёт страницу без перенаправления — «внимание»; перенаправление, закрытый порт 80 или ошибка — «в порядке»; нет ответа — «не ясно». SUV-WEB-015 (Referrer-Policy, W3C): unsafe-url и no-referrer-when-downgrade — «внимание», остальное и отсутствие заголовка (браузеры по умолчанию берут strict-origin-when-cross-origin) — «в порядке»; <meta name=referrer> важнее заголовка. SUV-WEB-004 теперь требует base-uri у политики на nonce или хэшах (CSP Evaluator): внедрённый <base> переносит скрипты с nonce на чужой сайт. SUV-SUB-004 (висящие NS): NS-сервер в домене, которого нет в DNS и который реестр называет свободным, — «проблема»; реестр не ответил — «внимание».

Почему везде 100%. Каждый случай, на котором Suveren ошибался, сначала исправлен и только потом закреплён на полигоне. Поэтому таблица показывает не «безошибочность», а то, что известные ошибки не вернутся. Как было до блока 1b, видно, если прогнать нынешний полигон на коде до него (коммит 598c23d, 2026-10-21):

До блока 1b После
Точный статус совпал 40 из 64 (62%) 64 из 64
Полнота (проблема найдена) 43 из 56 (77%) 56 из 56
Точность (тревога настоящая) 43 из 49 (88%) 56 из 56
Ложные тревоги 6 0
Пропуски 13 0

Ещё 5 случаев до блока были с неверной строгостью, хотя и в верную сторону: WARN вместо FAIL или наоборот (p=quarantine; pct=0, сломанный include в SPF, форма без согласия, HTTP без форм). Хуже всего было с TLS: сертификат на чужое имя получал OK, потому что доверие к сертификату не проверялось вовсе.

Что полигон нашёл в первом прогоне

Таблица выше — после исправлений. В первом прогоне (36 случаев) было 13 расхождений:

  • 9 — ошибка самого полигона: страница была слишком короткой, и Suveren честно счёл её оболочкой, которую собирают скрипты («не проверено»).
  • 1 — неверное ожидание: при DMARC p=none на вопрос «можно ли подделать письмо» ответ «да», то есть FAIL, а не WARN.
  • 3 — настоящие ошибки Suveren, исправлены:
  • битая ссылка на политику (404) засчитывалась как «политика опубликована»;
  • политика-заглушка без скриптов получала «не проверено» вместо «нет разделов»;
  • ссылка в подвале «Согласие на обработку персональных данных» засчитывалась как согласие рядом с формой. Теперь нужно утверждение посетителя: «соглашаетесь», «даю согласие».
  • Исправление третьей ошибки вскрыло на golden-корпусе четвёртую: форма поиска авиабилетов tutu.ru считалась формой с персональными данными из-за поля city_name_from (в имени есть «name»). Поля с названиями вещей (город, станция, аэропорт, отель, товар) теперь не считаются персональными данными.

Во втором прогоне добавлены 13 трудных случаев: CSP только в <meta>, версия jQuery только в заголовке файла, токен бота во встроенном скрипте, сертификат Минцифры, sp=none, 11 DNS-запросов SPF, домен без почты и другие. Единственное расхождение оказалось ошибкой случая: в тестовом токене Telegram был лишний символ.

Затем добавлены 15 случаев по находкам ревью и сверки с Observatory (см. ниже).

Сверка с Mozilla Observatory, 2026-10-22

18 сайтов из ночного списка. Сравнивались два способа:

  1. Отчёты Suveren против отчётов Observatory. Совпадение неполное, потому что Observatory смотрит из США (x-request-geoip-country-code: US), а Suveren — из России, и сайты отвечают по-разному. Пример: aviasales.ru отдаёт российскому посетителю HSTS, а американскому — страницу CloudFront без него.
  2. Те же заголовки — два судьи. Заголовки, которые получил Observatory (response_headers), прогоняются через проверки Suveren. Так сравнивается логика проверок, а не точка наблюдения.
Проверка Сравнений Совпало Расхождения
CSP (SUV-WEB-004) 18 18 —
HSTS (SUV-WEB-005) 18 18 до исправления — bi.zone (см. ниже)
Фреймы и nosniff (SUV-WEB-006) 18 17 avito.ru: X-Frame-Options: ALLOW-FROM
  • bi.zone: сайт отдаёт Strict-Transport-Security четыре раза с разными значениями. Observatory: «header cannot be recognized», Suveren: OK. Прав Observatory: директивы не должны повторяться (RFC 6797, 6.1). Suveren исправлен, теперь это WARN.
  • avito.ru: X-Frame-Options: ALLOW-FROM Observatory засчитывает, Suveren — нет. Здесь прав Suveren: ALLOW-FROM устарел, и современные браузеры его игнорируют (OWASP Secure Headers, MDN). Без frame-ancestors страницу можно встроить.

Сверка с SSL Labs, 2026-10-22

19 адресов через API SSL Labs (v3, кешированные результаты не старше 72 часов): 17 сайтов из ночного списка и два стенда badssl.com. Сравнивались принятие TLS 1.0/1.1 и доверие к сертификату с проверкой SUV-WEB-008, запущенной в то же время. Скрипт сохраняет reports/1b/ssllabs.json.

Сайты SSL Labs Suveren Совпало
habr.com, hh.ru, vk.com, 2gis.ru, dzen.ru, bi.zone, drom.ru, aviasales.ru, selectel.ru A+ … A-, TLS 1.0/1.1 нет OK да
mail.ru, positive.com, avito.ru, kontur.ru, tls-v1-0.badssl.com B: принимает TLS 1.0 и 1.1 WARN: принимает TLS 1.0 и 1.1 да
sberbank.ru T: сертификат не доверен (без учёта доверия — A) WARN: сертификат НУЦ Минцифры да
expired.badssl.com T: сертификат истёк FAIL: сертификат истёк да
ozon.ru A+ FAIL: сертификат истекает через 13 дней по разным правилам
tbank.ru оценки нет (SSL Labs не смог проверить) WARN: сертификат НУЦ Минцифры не с чем сравнить
gosuslugi.ru A+ проверка не показана: рукопожатие из этой сети не прошло не с чем сравнить

Совпало 16 из 16 сравнимых по протоколам и доверию. ozon.ru — не расхождение в логике: SSL Labs скорое истечение не оценивает, а Suveren считает срок меньше 14 дней проблемой (продлить нужно сейчас). Оценку SSL Labs Suveren не повторяет: шифры, длину ключей и уязвимости протокола он не проверяет. Сверялось только то, что проверяют оба.

testssl.sh не запускался и запускаться не будет: он посылает серверу пробы уязвимостей (Heartbleed и другие), а это уже не пассивная проверка.

Сверка с internet.nl, 2026-10-23

internet.nl — открытый сервис голландских властей и интернет-сообщества, эталонный по стандартам почты. Его тест почты запускался через сайт (для API нужна учётная запись) на восьми доменах; результаты лежат по постоянным ссылкам вида https://internet.nl/mail/<домен>/<номер>/. Сравнивались SPF, DMARC и DKIM с проверками SUV-MAIL-002, 003 и 004, запущенными в ту же ночь (--checks mail, JSON в reports/1b/internetnl2/). Записи DNS сверялись через резолвер Suveren (Яндекс DoT) и Cloudflare DoH: ответы совпали.

Домен (номер теста) internet.nl: SPF / DMARC / DKIM Suveren до сверки Suveren после Итог
habr.com (2069916) pass / pass / pass ok / warn / не ясно ok / warn / ok DMARC строже нарочно
ozon.ru (2069921) pass / pass / pass ok / warn / ok ok / warn / ok DMARC строже нарочно
wildberries.ru (2069919) pass / pass / pass ok / ok / ok ok / ok / ok совпало
tbank.ru (2069920) pass / pass / pass ok / ok / не ясно ok / ok / ok исправлено
vtb.ru (2069922) pass / рекомендация / pass ok / ok / не ясно ok / warn / ok исправлено
gosuslugi.ru (2069917) pass / pass / pass warn / ok / ok warn / ok / ok Suveren строже, по делу
sberbank.ru (2069924) pass / pass / pass ok / ok / ok ok / ok / ok совпало
nalog.gov.ru тест не завершился warn / fail / не ясно warn / fail / не ясно не с чем сравнить

Из 21 сравнимой пары (7 доменов × 3 проверки) до сверки совпало 14, после — 18 («рекомендация» internet.nl и «внимание» Suveren считаются совпадением). Три оставшихся расхождения — разница в строгости, а не ошибки; все разобраны ниже.

  • DKIM (habr.com, tbank.ru, vtb.ru). Ключ DKIM нельзя перечислить: имя записи содержит селектор, который знает только отправитель. Suveren перебирал распространённые селекторы и, не найдя, писал «не ясно». internet.nl поступает иначе: спрашивает _domainkey.<домен>. Если под этим именем есть хоть одна запись, DNS отвечает NOERROR (пустой промежуточный узел, RFC 2308), если нет — NXDOMAIN. Suveren взял этот приём (коммит 5cf8cc8) с одной защитой: сначала спрашивается случайное имя под доменом, и если на него тоже приходит NOERROR (в зоне *-запись или DNS-сервер так отвечает на всё), признак не используется. Теперь при найденном узле Suveren пишет «DKIM, вероятно, настроен, селектор неизвестен». Это всё ещё не доказательство подписи писем, поэтому в тексте предложено проверить заголовок DKIM-Signature.
  • DMARC vtb.ru. internet.nl дал «рекомендацию»: отчёты уходят на dmarc@vtb.com, а домен vtb.com не опубликовал запись vtb.ru._report._dmarc.vtb.com. По RFC 7489 (раздел 7.1) без неё почтовые сервисы отчёты на чужой домен не отправляют, и владелец vtb.ru их не получает. Suveren этого не проверял; теперь проверяет и предупреждает (коммит 276019e). У habr.com отчёты тоже уходят на чужой домен (habramail.net), но там запись есть, предупреждения нет.
  • DMARC habr.com и ozon.ru, p=quarantine. internet.nl засчитывает и quarantine, и reject. Suveren предупреждает: поддельное письмо при quarantine попадает в спам, а не отклоняется, и часть получателей его увидит. RFC 7489 допускает обе политики, reject — лучшая практика. Расхождение оставлено нарочно: Suveren строже.
  • SPF gosuslugi.ru. В записи есть a:mail.ntt.ru, а это имя не отвечает в DNS. По RFC 7208 (раздел 4.6.4) это «пустой» запрос; до двух таких запросов запись остаётся рабочей, поэтому internet.nl её засчитывает. Suveren тоже не считает это ошибкой (не FAIL), но предупреждает: с адресов этого имени письма уже не пройдут SPF, а ещё одно-два таких имени сломают всю запись. Расхождение оставлено.
  • nalog.gov.ru. Тест internet.nl дважды завершился ошибкой на этапе STARTTLS (почтовый сервер eups.nalog.ru, видимо, не отвечает из Нидерландов), и страницы с результатом нет. Выводы Suveren проверены вручную через два резолвера: SPF-записи нет (в TXT только sputnik-verification и идентификатор), _dmarc пуст, _domainkey отвечает NXDOMAIN. Выводы верны.

Попутно найдена грамматическая ошибка в тексте SPF («за ними» при одном имени), исправлена.

Повторяемость, 2026-10-23

Если два прогона подряд по одним и тем же сайтам дают разные ответы, ночное сравнение показывает дрожание инструмента, а не изменения сайтов. Методика: 40 сайтов (каждый седьмой из tools/nightly-sites.txt), suveren batch --dns ru -j 4 два раза подряд, сравнение статусов по ID проверок (tools/nightly.py, функция compare).

Сначала тот же набор сравнили с ночным прогоном. Это нашло три источника дрожания, все исправлены:

Что Пример Исправление
Проверка TLS пропадала из отчёта, если одно соединение не удалось profi.ru: нет SUV-WEB-008 повтор рукопожатия, затем «не проверено»; правило самоаудита check-vanished (e3a45e8)
Меняющиеся карточки на главной забирали весь бюджет внутренних страниц fl.ru: пять случайных /users/…/portfolio по одной странице каждого вида (2362f21)
Таймаут страницы политики давал WARN kp.ru: политика «не открылась» в одном прогоне из двух повтор после таймаута, затем «не проверено» (2362f21)

Два прогона подряд после этих исправлений:

Значение
Сайтов совпало полностью 36 из 40
Совпало статусов проверок 1066 из 1081 (98,6%)
Противоречий в отчётах (suveren audit) 0 и 0

Четыре расхождения: - dixy.ru, docdoc.ru, okko.tv: во втором прогоне раздел не проверился (стена от ботов, DNS-ответ для почты), и оценка помечена «неполная, сравнивать нельзя». Пометка сработала так, как задумано. - okko.tv: ещё и DNS-серверы домена сменились между прогонами (Selectel → EdgeCenter). Похоже на переезд самого сайта. - sravni.ru: страница политики ответила 404 в первом прогоне и открылась во втором. 404 даёт предупреждение, поэтому теперь его запрашивают дважды (c4b8702).

Ревью «чужими глазами», 2026-10-22

Два независимых ревьюера без контекста проверяли код против стандартов: один — почту и веб-безопасность, другой — 152-ФЗ и штрафы. Вместе — 22 находки:

  • 20 исправлено, у каждой есть случай на полигоне или тест (коммиты 0ee3ffa, 9730788, 30350c3, 9158df1, beca70f, 56558f7);
  • в одной ревьюер ошибся (Bootstrap, см. ниже);
  • одна оставлена (v=dmarc1, см. ниже).

Оставлено как есть:

  • v=dmarc1 строчными буквами: по грамматике RFC 7489 (6.4) версия регистрозависима, но крупные получатели, похоже, принимают и такую запись. Оставлено, пока нет данных о реальном поведении.
  • Согласие одной фразой с «политикой конфиденциальности» («соглашаюсь с политикой и даю согласие на обработку»). Политика только информирует, и считать ли такое согласие «не отдельным» — вопрос толкования. Предупреждения нет.
  • Счётчик «проверено источников кода» учитывает и файлы, которые не скачались. Это мелкое завышение в тексте, на статус оно не влияет.

В данных об уязвимостях ревьюер ошибся: CVE-2024-6484 и CVE-2024-6531 (Bootstrap) отозваны в NVD, а диапазон CVE-2019-8331 в Suveren совпадает с NVD. Поэтому утверждения ревьюеров сверялись с первоисточником, а не принимались на веру.

Утечки из форм до отправки, 2026-10-24

Эксперимент --form-leaks (SUV-PD-012) работает только в браузере, поэтому полигон tests/lab.py его не проверяет: полигон гоняет код без браузера. Его роль играют локальные страницы в tests/test_formleaks_browser.py. Тест поднимает два сервера на 127.0.0.1 (сайт и «сторонний сервис») и открывает их в настоящем браузере. Ответ записан до запуска.

Что на странице Ожидание Результат
Скрипт шлёт имя, телефон, почту и пароль при выходе из поля (fetch с JSON) все четыре — открытым текстом совпало
Страница сама вызывает form.submit() и requestSubmit() обе попытки остановлены, на сервер не пришло ни одного POST совпало
Маска телефона принимает только номера на 9 номер стёрт до выхода из поля, обработчик blur видит пустое поле совпало
Поля карты (cc-number), города, поиска не заполняются совпало
Форма во всплывающем окне (скрыта) заполняется программно совпало
«Запись сессий»: нажатия копятся, сжимаются gzip и уходят Blob-маяком при уходе со страницы имя и почта найдены «в сжатом виде» совпало
Рекламный пиксель: SHA-256 почты в адресе картинки почта найдена «в виде хеша SHA-256» совпало
«Ловец заявок» шлёт телефон самому сайту не утечка, а строка «сам сайт получает введённое» совпало
Получатель неизвестной страны «внимание», не «проблема» совпало

Ещё 18 юнит-тестов (tests/test_formleaks.py) проверяют разбор: base64 со всеми тремя сдвигами внутри длинной строки, двойное URL-кодирование, \u-экранирование JSON, MD5 телефона с 7 впереди, кадры WebSocket, а также то, что цифры номера внутри длинного числа не считаются телефоном.

Чего Chrome не показывает, поэтому сделано иначе. Тело маяка из Blob и запросы, ушедшие при закрытии вкладки, не видны ни в событиях Playwright, ни в DevTools-протоколе (проверено на локальной странице). Поэтому sendBeacon, fetch, XMLHttpRequest и WebSocket.send обёрнуты до загрузки скриптов сайта, и копия каждого тела сразу уходит в Suveren. Вкладку без окна нельзя скрыть по-настоящему, поэтому «уход посетителя» изображается: состояние hidden, события visibilitychange и pagehide. На них записывающие скрипты отправляют накопленное.

Прототип на живых сайтах. 22 российских сайта с формами заявки (клиники, застройщики, автодилеры, обучение, магазины), один прогон, без отправки форм. Результаты: - «в порядке» — 15; - «внимание» — 1: российский сервис чата получает почту из поля подписки открытым текстом до нажатия кнопки. Это его функция сбора контактов из форм; - «не проверено» — 4: на двух не нашлось полей; ещё на двух мешала бот-стена при обычном HTTP. После этого эксперимент запускается в браузере и за такой стеной; - 2 сайта отчёта не дали.

Названия сайтов не публикуются: эксперимент на чужих сайтах — только с согласия владельца, и прототип был единственным таким прогоном. Что прототип исправил: - маски вида «+7 (9__)» не принимают тестовый номер: отчёт теперь прямо говорит, что телефон не проверен, а владелец может указать свой номер (--form-phone); - тела Blob-маяков и запросы при уходе со страницы не были видны (см. выше); - страница, ушедшая на другой адрес, роняла эксперимент; - получатель назывался по хостингу («через Selectel»), а не по сервису: домен API добавлен в базу сервисов.

Чего эксперимент не видит: - Вебвизор Яндекс Метрики. Он шлёт запись через WebSocket wss://mc.yandex.ru/solid.ws, и в прогонах сервер Метрики не включил его ни на одном сайте, хотя в коде счётчика webvisor:true стоит на 87 из 297 сайтов ночного списка. Записывает ли Вебвизор поля так, чтобы это было видно, не проверено. По справке Метрики поля по умолчанию не записываются (опция «Записывать все поля»), а пароли не записываются никогда. - Зашифрованное содержимое, а также формы внутри iframe и запросы из Web Worker'ов. - Скрытые поля получают программные события, у которых isTrusted равно false. Скрипт, который их отбрасывает, не сработает, и находку можно пропустить. - Состояние «согласие на cookies дано». Баннеры не нажимаются, поэтому проверяется поведение до согласия: юридически это самый важный случай.

Ограничения

  • Случаи написал тот же автор, что и проверки, а значит, и его слепые пятна. Поэтому полигон дополняют ревью «чужими глазами» и сверка с эталонными инструментами.
  • Синтетический сайт проще настоящего. Ложные тревоги на настоящей вёрстке ловят golden-корпус и ночной прогон.
  • По несколько случаев на проверку: проценты показывают, что проверка работает на типичных нарушениях, а не статистическую точность на всей сети.