Skip to content

ТЗ — Attack-monitor (статистика атак) (cms/attack-monitor)

Слой: 🔵 инфра-модуль, обязательный в managed-парке · Зрелость доноров: ★ · Донор: catalog, er, freelance Статус: ТЗ к разработке

Назначение и возможности

Реализация раздела «Модуль „Статистика атак“» из /cms-v2/security: CMS видит, что её атакуют, сама замедляет атакующего и отчитывается студии. Не полноценный WAF — детект, статистика и автоблокировка на прикладном уровне (в дополнение к периметру Caddy).

  • Детекторы: SQLi/XSS-паттерны в запросах, зонды по чужим CMS (/wp-login.php и т.п.), всплеск 404;
  • брутфорс логина (админка, кабинеты, API-токены), флуд форм — модуль потребляет сигналы honeypot/rate-limit ядра и капчи cms/antispam, физический rate-limit сам не реализует;
  • append-only журнал attack_events: ip, тип сигнала, паттерн, url, user-agent, счётчики повторов;
  • агрегаты (топ-IP, топ-паттернов, динамика по дням) и ретеншн с выгрузкой для отчётности;
  • эскалация в 4 ступени: rate-limit ядра (гасит запросы физически, вне модуля) → блокировка IP на уровне приложения (attack-monitor, по журналу и порогам) → экспорт блок-листа для Caddy (бан до PHP) → алерты и телеметрия парка;
  • whitelist доверенных IP (студия, мониторинг), разбан в один клик;
  • Filament-дашборд атак с фильтрами по типу/IP/периоду, картой атак по гео;
  • запись сигналов асинхронная — сам модуль не становится узким местом или новой целью для DoS во время атаки, которую он же детектирует;
  • детектор корреляции с аутентификацией: сопоставляет провалы логина с каноническими событиями ядра UserLoggedIn/UserPasswordChanged (ревизия ядра 14.07.2026, п.4) — успешный вход сразу после серии провалов помечается как вероятная компрометация учётки, не просто «атака отражена»;
  • детектор кросс-сигнальной корреляции: комбинация разных сигналов с одного IP/подсети в окне времени даёт составной риск-скор выше суммы отдельных порогов — эскалация быстрее, чем по одиночному сигналу.

Зависимости и выключение

requires: ядро (rate-limit, honeypot, канонические события аутентификации канала 1) · suggests: cms/antifraud, cms/telegram (алерты), geo-provider (гео-резолв IP для дашборда, реестр §2 стандарта) · provides: attack-detection

Поведение при выключении: остаётся только базовый rate-limit ядра без журналирования, эскалации и экспорта блок-листа в Caddy — деградация до штатной защиты форм, без видимости атак и автоблокировки на уровне приложения. Модуль обязателен в managed-парке студии, отключение допустимо только на самостоятельном (не managed) хостинге клиента.

Стоимость внешних API: не применимо — модуль не делает прямых платных вызовов; алерты через cms/telegram бесплатны, экспорт блок-листа на Caddy и запрос к geo-provider — внутренние каналы без тарификации.

Модель данных

ТаблицаКлючевые поляПримечание
cms_attack_eventsid, ip, asn, geo, type, pattern, url, user_agent, verdict, correlated_signals, created_atappend-only журнал сигналов
cms_attack_blocksid, ip, reason, expires_at, escalation_levelактивные блокировки с TTL и уровнем эскалации
cms_attack_whitelistid, ip, label, added_byдоверенные IP (не подпадают под блокировку)

cms_attack_events — append-only, BRIN-индекс по created_at (ретеншн-джоба чистит по retention_days); индекс на ip под агрегаты топ-IP. type/verdict — PHP Enum. correlated_signals (nullable JSON, cast array) — заполняется воркером при кросс-сигнальной корреляции: список сигналов и составной риск-скор, null для одиночных событий. cms_attack_blocks — индекс (ip, expires_at) под проверку активных блокировок и экспорт блок-листа.

ПДн-паспорт (152-ФЗ). cms_attack_events хранит ip и user_agent — потенциально идентифицирующие данные; прямого user_id нет — провал логина по аккаунту логируется обезличенно (пара (ip, аккаунт) для порога, без ФИО/email) — это и есть минимизация ПДн для модуля. Срок хранения — retention_days (дефолт 180 дней), чистка — джобой prune. Агрегаты для долгосрочной отчётности переживают удаление сырых событий и не считаются ПДн-хранилищем — только счётчики, без ip/user_agent построчно.

PrivacyRegistry (п.15 ревизии ядра): IP в связке с иными данными признаётся ПДн по 152-ФЗ, но модуль не регистрирует обработчик в PrivacyRegistry — журнал атак ведётся в рамках законного интереса безопасности (защита сайта от атак), хранится по собственному ретеншну (retention_days), а не по запросу субъекта, и не входит в cms:privacy:export/cms:privacy:forget: связь «IP → учётная запись» модуль не хранит (провал логина — обезличенная пара (ip, аккаунт), без ФИО/email). Выгрузка/удаление записей по конкретному IP при обоснованном запросе субъекта — вручную студией, не автоматическим каскадом ядра.

Входные и выходные данные

Входы:

ИсточникДанные/поляЧем валидируется
Middleware ядра (перехват запросов)ip, url, user_agent, паттерн запросане FormRequest — сигнатурный детектор внутри middleware, работает до контроллера, не доверяет входу вовсе (сам ищет аномалии)
Провал логина (событие ядра)ip, аккаунт/логин (обезличенно), timestampсчитается через штатный механизм ядра (rate-limit/попытки аутентификации), модуль только агрегирует
События ядра UserLoggedIn/UserLoggedOut/UserPasswordChanged (канал 1, канон. DTO)user_id, ip, timestampподписка на DTO ядра, не на Illuminate\Auth\Events\* напрямую (ревизия ядра 14.07.2026, п.4); только для корреляции с уже зафиксированными сигналами по тому же IP
Срабатывание honeypot/капчи/rate-limit (события форм ядра)ip, форма, timestampприходит уже провалидированным событием — модуль не парсит тело формы сам
geo-provider (suggests, реестр §2 стандарта)гео по IP (страна/регион)provides-контракт; при отсутствии провайдера — geo пустой в событии, не блокирует запись
cms/antifraud (suggests)агрегированные сигналы через fraud-signalprovides-контракт, не сырые данные — антифрод сам решает, что агрегировать
Filament: whitelist IPip, labelFormRequest: валидный IP/CIDR, дедуп по ip
POST /admin/attack-monitor/whitelist, DELETE /admin/attack-monitor/blocks/{id}ip/id блокировкиPolicy attack-monitor.manage

Выходы:

ПотребительДанныеФормат
GET /internal/attack-monitor/blocklistсписок активных блокировок (IP, TTL)подписанный внутренний JSON-эндпоинт для Caddy
Filament (дашборд, список событий/блокировок)агрегаты, журналтаблицы/графики
cms/telegram (suggests)алерт при превышении порогасообщение через notification-channel
Студийная телеметрия парка (внешний канал)агрегированные метрики атаксогласно контракту флит-мониторинга (вне /api/v1)
provides-контракт attack-detectionтекущая картина угроз сайтапотребляется студийным мониторингом парка
события AttackDetected/AttackEscalated/AttackAlertThresholdExceededсм. «События и обмен»payload события

Настройки (группа attack-monitor)

brute_force_threshold, alert_threshold_per_hour, max_events_per_ip_per_minute — лимиты/квоты стандарта (§6): дефолт у каждого, достижение порога — понятное поведение (блокировка/алерт/агрегация), не 500 и не тихое обрезание.

КлючТипДефолтaffectsPageCacheОписание
attack-monitor.brute_force_thresholdint5нетПровалов логина до блокировки
attack-monitor.block_duration_minutesint60нетБазовая длительность блокировки (растёт при повторе)
attack-monitor.escalate_to_caddybooltrueнетЭкспортировать блок-лист на периметр Caddy
attack-monitor.alert_threshold_per_hourint50нетПорог событий/час для алерта студии
attack-monitor.retention_daysint180нетХранение журнала attack_events
attack-monitor.known_bot_whitelist_sourceenum(manual/user_agent_verified)manualнетКак признаётся легитимный бот (поисковики и т.п.): вручную по IP или через верифицированный реверс-DNS/UA-паттерн
attack-monitor.event_write_queuestringattack-monitorнетОчередь для асинхронной записи событий — модуль не пишет в БД синхронно в горячем пути запроса
attack-monitor.max_events_per_ip_per_minuteint120нетПредохранитель: свыше этого числа однотипных событий с одного IP в минуту — агрегируются в одну запись со счётчиком, не по одной строке на событие
attack-monitor.correlation_window_minutesint30нетОкно, в котором разные сигналы с одного IP/подсети (и события UserLoggedIn/UserPasswordChanged) считаются связанными для составного риск-скора
attack-monitor.correlation_risk_thresholdint100нетСоставной риск-скор, при котором кросс-сигнальная корреляция форсирует эскалацию раньше одиночных порогов
attack-monitor.blocklist_export_formatenum(caddy/fail2ban)caddyнетФормат экспорта блок-листа: caddy — внутренний JSON для периметра студии, fail2ban — текстовый список IP для клиентских сайтов вне managed-парка без Caddy студии
attack-monitor.emergency_disable_blockingboolfalseнетKill-switch: переводит модуль в режим «только детект» — активная блокировка и эскалация на Caddy отключены, журнал и дашборд продолжают писать/показывать данные; для инцидента ложных срабатываний без потери видимости атак

API

МетодПутьДоступНазначение
GET/api/v1/admin/attack-monitor/eventsadmin (attack-monitor.view)Список событий с фильтрами
GET/api/v1/admin/attack-monitor/blocksadmin (attack-monitor.view)Активные блокировки
POST/api/v1/admin/attack-monitor/whitelistadmin (attack-monitor.manage)Добавить IP в whitelist
DELETE/api/v1/admin/attack-monitor/blocks/{id}admin (attack-monitor.manage)Снять блокировку (разбан)
GET/api/v1/internal/attack-monitor/blocklistвнутренний (Caddy-экспорт, подпись)Текущий блок-лист для reverse proxy

Списки событий и блокировок — keyset-пагинация (не OFFSET), под большой объём attack_events.

Компоненты

Filament: дашборд атак (топ-IP, топ-паттернов, динамика), список активных блокировок с разбаном в один клик, whitelist-редактор, виджет-карта атак по гео (топ страны/регионы) — гео резолвится через geo-provider (suggests, реестр §2 стандарта), без провайдера — карта без гео-разбивки (деградация, не ошибка). Команды: cms:attack-monitor:sync-caddy --json (экспорт, формат — blocklist_export_format: caddy для периметра студии, fail2ban — текстовый список IP для клиентских сайтов вне managed-парка без Caddy студии), cms:attack-monitor:prune --json (ретеншн), cms:attack-monitor:doctor --json.

Ранбук (симптом → команда):

СимптомЧто проверитьКоманда
Волна ложных блокировок легитимных ботов/партнёровknown_bot_whitelist_source, UA/реверс-DNS попавших под бан IPдобавить IP в whitelist, затем cms:attack-monitor:sync-caddy --json
Caddy не синкается с блок-листом (баны не доезжают до периметра)доступность подписанного эндпоинта /internal/attack-monitor/blocklist, сеть между Caddy и приложениемcms:attack-monitor:sync-caddy --json (смотреть код ответа и ретраи), при повторных сбоях — cms:attack-monitor:doctor --json
Всплеск брутфорса / подозрение на компрометацию учёткиbrute_force_threshold, свежие AttackDetected/корреляция с UserLoggedIn по тому же IPcms:attack-monitor:doctor --json, при подтверждении — принудительный сброс сессий скомпрометированной учётки (сервис ядра, вне модуля)

События и обмен

СобытиеКогдаPayload
AttackDetectedсработал детекторip, type, pattern, url
AttackEscalatedэскалация на следующую ступеньip, escalation_level
AttackAlertThresholdExceededпревышен порог событий/часcount, window
AttackCorrelationDetectedуспешный UserLoggedIn/UserPasswordChanged сразу после серии провалов с того же IP в окне correlation_window_minutesip, user_id, prior_failures, event_type
AttackCompositeRiskEscalatedсоставной риск-скор кросс-сигнальной корреляции превысил correlation_risk_thresholdip, signals[], score

Слушает: сигналы cms/antifraud (агрегированные аномалии); канонические события ядра UserLoggedIn/UserLoggedOut/UserPasswordChanged (канал 1, ревизия ядра 14.07.2026, п.4) — не как источник новых провалов (те по-прежнему считает штатный механизм попыток аутентификации ядра), а для корреляции с уже зафиксированными сигналами атаки по IP (детали — в таблице взаимодействий ниже). Provides-контракт attack-detection — потребляется студийным мониторингом парка; алерты уходят через cms/telegram (канал notification-channel), если модуль включён.

Таблица взаимодействий:

Сущность/модульКаналНаправлениеЧто происходит
Ядро — middleware/rate-limit/honeypotсервис-вызов (requires) + перехват запросаinдетекторы работают на уровне middleware ядра до контроллера, используя уже существующие механизмы rate-limit/honeypot как источник части сигналов
Ядро — события аутентификации (канал 1)события UserLoggedIn/UserLoggedOut/UserPasswordChanged (requires)inкорреляция уже зафиксированных сигналов атаки по IP с успешным входом/сменой пароля — сигнал компрометации, не новый источник провалов
cms/antifraud (suggests)provides-контракт fraud-signal (потребление)inагрегированные аномалии добавляются в общую картину атак без прямого чтения чужих таблиц
geo-provider (suggests)provides-контракт geo-provider (потребление)inгео по IP для дашборда и виджета-карты атак; при отсутствии — geo пустой, не блокирует запись события
cms/telegram (suggests)provides-контракт notification-channeloutалерт студии при превышении alert_threshold_per_hour; без модуля — только запись в журнал, без внешнего уведомления
Caddy (reverse proxy, внешний относительно CMS)внутренний подписанный HTTP-эндпоинт /internal/attack-monitor/blocklistoutпериодический перечит блок-листа Caddy для бана на периметре — не входит в 5 внутренних каналов (внешний технический канал, аналог cms/webhooks-in но исходящий)
Студийная телеметрия паркаprovides-контракт attack-detectionoutфлит-дашборд агрегирует картину атак по всем managed-сайтам
Очередь attack-monitor.event_write_queueочередь (канал 5)in→outвсе детекторы кладут «сырое» событие в очередь, запись в cms_attack_events и оценка эскалации происходят в воркере, не в веб-запросе

Фоновая работа

Именованные очереди attack-monitor (запись событий, экспорт блок-листа sync-caddy, ретеншн prune) — все идемпотентны. Запись событий в журнал — асинхронная по умолчанию: детектор в middleware кладёт лёгкое сообщение в очередь event_write_queue и немедленно продолжает обработку исходного запроса; тяжёлая работа (запись в БД, пересчёт агрегатов, проверка порога эскалации, расчёт составного риск-скора кросс-сигнальной корреляции и проверка UserLoggedIn/UserPasswordChanged за окно correlation_window_minutes) — в воркере, переиспользует индекс ip на cms_attack_events, новых тяжёлых запросов не добавляет. Внешние вызовы (экспорт на Caddy, алерт студии) — только из очередей. Расписание — через ScheduleRegistrar ядра: ретеншн ежедневно, синк блок-листа по мере эскалаций.

Производительность и кеш

  • Ожидаемый объём: в спокойном режиме — единицы–десятки событий в сутки; под атакой/сканированием — сотни–тысячи событий в минуту с одного или множества IP — это профильная нагрузка модуля, не аномалия для него;
  • горячий путь — детект в middleware на каждом HTTP-запросе к сайту: бюджет 0 синхронных запросов к БД на нормальный запрос (проверка блокировки IP — из кеша attack-monitor:blocklist, не SELECT на каждый хит); запись события — только постановка в очередь (не блокирующая операция);
  • под атакой один IP может сгенерировать тысячи однотипных событий за минуты — max_events_per_ip_per_minute агрегирует их в одну запись со счётчиком повторов вместо строки на каждое событие (иначе журнал и очередь сами становятся вектором исчерпания ресурсов — «атака на систему защиты от атак»);
  • индекс ip на cms_attack_events критичен под агрегаты топ-IP; BRIN по created_at — под ретеншн и динамику по дням, дешевле B-tree при append-only потоке высокого объёма; (ip, expires_at) на cms_attack_blocks — под проверку активных блокировок при синхронизации с Caddy и при чтении из кеша blocklist;
  • кешируется: attack-monitor:blocklist — список активных заблокированных IP, читается на каждом запросе (или через периметр Caddy, минуя PHP вовсе при escalate_to_caddy=true — тогда бюджет ещё ниже, PHP не видит повторные запросы забаненного IP); инвалидация — событиями AttackDetected/AttackEscalated;
  • дашборд-агрегаты (топ-IP, топ-паттернов, динамика) кешируются отдельно с коротким TTL (секунды–минуты) — не пересчитываются на каждый заход в Filament при высокой частоте записи в журнал.

Безопасность

Экспорт блок-листа для Caddy — по подписанному внутреннему эндпоинту, не публичен. Whitelist-IP не подпадает под блокировку ни на одной ступени эскалации. Журнал attack_events — append-only, записи не редактируются и не удаляются вручную (только ретеншн-джобой). В журнал и агрегаты не попадают ПДн сверх IP/user-agent, необходимых для детекта (полный разбор — ПДн-паспорт в «Модели данных»). Векторы, специфичные для модуля:

  • атака на сам attack-monitor (вал запросов должен не только атаковать сайт, но и забить журнал/очередь атак) — запись асинхронная и агрегируемая (max_events_per_ip_per_minute), очередь event_write_queue изолирована от остальных очередей сайта, чтобы флуд атаки не блокировал обработку писем/индексации;
  • подмена источника при экспорте блок-листа — внутренний эндпоинт для Caddy подписан (HMAC/signed URL), не публичен и не индексируется;
  • автоблокировка легитимного бота (поисковый краулер, партнёрский мониторинг) — known_bot_whitelist_source даёт выбор между ручным whitelist и верификацией по реверс-DNS/UA-паттерну; без этого модуль рискует забанить Googlebot за высокую частоту обхода, что бьёт по SEO сильнее любой атаки;
  • обход детекта через распределённые IP (botnet, ротация прокси) — детект по паттерну запроса (сигнатуры SQLi/XSS/зонды) не зависит от повторяемости IP, ловит единичные подозрительные запросы независимо от источника;
  • манипуляция ASN/geo-данными — используются только для отображения в дашборде (контекст для человека), не как единственное основание блокировки.

Матрица ролей:

Рольattack-monitor.view (дашборд, журнал, блокировки)attack-monitor.manage (whitelist, разбан, kill-switch)
Посетительнетнет
Редакторнетнет
Менеджернетнет
Админ клиентаданет — ошибочный разбан снимает активную защиту и открывает атаку заново, поэтому только повышенная роль
Studio-рольдада

Разбан IP и правка whitelist — только studio-роль (правило ядра: managed-модули отключает только studio-роль с подтверждением, см. стандарт §3). emergency_disable_blocking (kill-switch) — тоже attack-monitor.manage, но фиксируется в аудите отдельно от обычного разбана, так как затрагивает весь сайт.

UX-требования

Админ:

  • пустой журнал событий — «атак не зафиксировано» вместо пустой таблицы (это хорошая новость, UI должен это отражать, а не выглядеть как сломанный дашборд);
  • массовые действия: «разбанить выбранные IP», «добавить топ-N атакующих IP в постоянный блок-лист»;
  • ошибка «Caddy не отвечает на синк блок-листа» — понятная подсказка о деградации (эскалация на периметр не работает, но блокировка на уровне приложения продолжает действовать), не тихий провал команды;
  • подтверждение необратимого — снятие IP из блокировки с историей повторных нарушений предупреждает «этот IP уже банился N раз», прежде чем разбанить;
  • дашборд показывает состояние эскалации сайта одним взглядом (спокойно / повышенная активность / атака) — не требует чтения сырого журнала для оценки ситуации;
  • виджет-карта атак по гео — при недоступном geo-provider показывает карту без разбивки и подпись «гео недоступно», не пустой/сломанный блок;
  • при включённом emergency_disable_blocking дашборд показывает баннер «режим только детект — блокировка отключена вручную», чтобы админ не принял тишину за штатную.

Посетитель (в т.ч. заблокированный):

  • заблокированный IP получает нейтральный ответ (429/403 с общим текстом), не раскрывающий детали детектора (тип сигнатуры, порог) — иначе атакующий калибрует обход;
  • легитимный пользователь за NAT/офисной сетью, где кто-то другой словил блокировку — видит понятную страницу с возможностью связаться с поддержкой, не голый 403 без объяснений (баланс: не раскрывать детали детектора, но не оставлять человека совсем без выхода).

Крайние случаи и типовые баги

  • сам модуль под нагрузкой атаки → запись событий асинхронная (event_write_queue) и агрегируемая (max_events_per_ip_per_minute) — детектор в middleware не делает синхронный INSERT на каждый запрос, атака не превращается в DoS на очередь/таблицу модуля;
  • автоблокировка легитимного бота (Googlebot, Яндекс.Бот, партнёрский аптайм-монитор) → known_bot_whitelist_source и whitelist по IP/подсети; контрактный тест — верифицированные боты не блокируются даже при высокой частоте запросов;
  • ротация журнала атакretention_days + BRIN-индекс: ретеншн-джоба удаляет события старше порога регулярно, не даёт таблице расти бесконечно; агрегаты для долгосрочной отчётности (топ-паттернов по месяцам) считаются до удаления сырых событий, если такая отчётность нужна дольше retention_days;
  • гонка: параллельные события эскалации по одному IP → эскалация идемпотентна — повторная обработка события того же IP в том же окне не поднимает уровень дважды (проверка текущего escalation_level перед инкрементом, атомарно);
  • выключение модуля посреди активной блокировки → активные блокировки в cms_attack_blocks не применяются на уровне приложения без модуля (только rate-limit ядра остаётся); если escalate_to_caddy уже экспортировал блок-лист — он продолжает действовать на периметре до истечения TTL, даже если модуль выключен в приложении (Caddy не знает о состоянии CMS) — это не баг, а следствие устройства периметра, фиксируется в docs/module.md;
  • отсутствие cms/antifraud (suggests не установлен) → модуль работает на собственных сигнатурных детекторах и брутфорс-детекте, просто без расширенной картины аномалий — деградация, не ошибка;
  • отсутствие cms/telegram (suggests не установлен) → AttackAlertThresholdExceeded издаётся как обычно, алерт остаётся только записью в журнал/дашборд, без внешнего уведомления;
  • сбой/недоступность Caddy на синке блок-листаcms:attack-monitor:sync-caddy ретраит с backoff, между попытками блокировка продолжает действовать на уровне приложения (не полагается только на периметр) — деградация не «дырявая»;
  • VPN/NAT — несколько пользователей за одним IP → брутфорс-детект по логину считает провалы по паре (ip, аккаунт), не по голому IP, чтобы не банить всю офисную сеть за одного человека с опечаткой в пароле; порог brute_force_threshold документируется как требующий калибровки для известных NAT-адресов клиента (whitelist вручную, как и для ботов);
  • огромный объём событий при целенаправленной DDoS-подобной кампании → агрегирование по IP (max_events_per_ip_per_minute) плюс эскалация на Caddy — когда прикладной уровень перегружен, решение — банить раньше PHP, не глубже оптимизировать сам модуль; граница ответственности модуля (не WAF) фиксируется в «Назначении»;
  • противоречивые настройки (escalate_to_caddy=true, но Caddy недоступен постоянно) → ⚠️ Противоречие: ТЗ не описывает поведение health-чека в этом случае. Разрешение: health-чек модуля (манифест) обязан отражать «эскалация на Caddy недоступна» отдельным статусом, не смешивая с общим health ядра — админ видит деградацию конкретно этой ступени, не общий «всё плохо»;
  • успешный вход сразу после серии провалов с того же IPAttackCorrelationDetected не блокирует уже вошедшего (сессия штатно создана ядром), только поднимает приоритет расследования и алертит студию; принудительный логаут скомпрометированной учётки — вне зоны ответственности attack-monitor;
  • geo-provider не установлен/недоступен → гео в событиях и на виджете-карте пустое, детект и блокировка работают как обычно — деградация только визуализации;
  • emergency_disable_blocking включён, но Caddy уже забанил IP ранее → баны на периметре живут до истечения TTL (Caddy не знает о kill-switch), новые не создаются — тот же принцип, что при выключении модуля, фиксируется в docs/module.md;
  • повторный прогон import-legacy → идемпотентно по ip: существующие записи блок-листа/whitelist обновляются, не дублируются.

Донорский код

Что взятьПуть
Подходы детекта накрутки/аномалийcatalog, er
Репутационные сигналыfreelance/project/src/app/Domains/Reputation/

Миграция legacy-данных. cms:attack-monitor:import-legacy --source=<профиль> — перенос накопленного на донорской площадке блок-листа и whitelist доверенных IP при миграции клиента, чтобы известные плохие/хорошие IP не терялись при переезде. Маппинг профиля источника → cms_attack_blocks/cms_attack_whitelist; идемпотентна, ключ дедупа — ip; --dry-run — отчёт расхождений без записи. Прогон на копии донорских данных — часть приёмки модуля.

Тесты и приёмка

  • [ ] Контрактный тест: превышение brute_force_threshold по логину блокирует IP на block_duration_minutes;
  • [ ] Журнал attack_events append-only — записи не редактируются и не удаляются вручную (только ретеншн-джобом);
  • [ ] Экспорт блок-листа для Caddy проходит по подписанному внутреннему эндпоинту, не публичен;
  • [ ] Whitelist-IP не подпадает под блокировку ни на одной ступени эскалации;
  • [ ] Алерт студии срабатывает при превышении alert_threshold_per_hour (через доступный канал уведомлений);
  • [ ] Деградация при выключении оставляет базовый rate-limit ядра, не открывает формы без защиты;
  • [ ] Ретеншн retention_days реально очищает старые события job'ом, не бессрочное хранение;
  • [ ] Запись событий асинхронная — нагрузочный тест подтверждает, что детект в middleware не делает синхронный INSERT;
  • [ ] Флуд с одного IP агрегируется по max_events_per_ip_per_minute, не создаёт строку на каждое событие;
  • [ ] Верифицированный бот (по known_bot_whitelist_source) не блокируется при высокой частоте запросов;
  • [ ] Брутфорс-детект считает провалы по паре (ip, аккаунт), не банит целую NAT-подсеть за одного пользователя;
  • [ ] Корреляция: событие UserLoggedIn сразу после серии провалов по IP порождает AttackCorrelationDetected с повышенным приоритетом, не блокирует уже вошедшую сессию;
  • [ ] Составной риск-скор (несколько разных сигналов с одного IP в окне correlation_window_minutes) эскалирует по correlation_risk_threshold быстрее, чем любой из сигналов по одиночному порогу;
  • [ ] Kill-switch emergency_disable_blocking=true отключает блокировку/эскалацию на Caddy, детект и дашборд остаются рабочими (контрактный тест на «только журналирование»);
  • [ ] cms:attack-monitor:import-legacy идемпотентен (повторный прогон по ключу ip не дублирует записи), --dry-run не пишет в БД;
  • [ ] Виджет-карта атак по гео деградирует без geo-provider (пустая гео-разбивка), не падает и не ломает дашборд;
  • [ ] Контрактный набор cms-testing зелёный, testbench-изоляция пакета, feature-тест на каждый роут;
  • [ ] Тестовая БД только attack-monitor_test; migrate:fresh/refresh/reset, db:wipe запрещены.

Внутренняя база знаний студии