Тема
ТЗ — 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_events | id, ip, asn, geo, type, pattern, url, user_agent, verdict, correlated_signals, created_at | append-only журнал сигналов |
cms_attack_blocks | id, ip, reason, expires_at, escalation_level | активные блокировки с TTL и уровнем эскалации |
cms_attack_whitelist | id, 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-signal | provides-контракт, не сырые данные — антифрод сам решает, что агрегировать |
| Filament: whitelist IP | ip, label | FormRequest: валидный 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_threshold | int | 5 | нет | Провалов логина до блокировки |
attack-monitor.block_duration_minutes | int | 60 | нет | Базовая длительность блокировки (растёт при повторе) |
attack-monitor.escalate_to_caddy | bool | true | нет | Экспортировать блок-лист на периметр Caddy |
attack-monitor.alert_threshold_per_hour | int | 50 | нет | Порог событий/час для алерта студии |
attack-monitor.retention_days | int | 180 | нет | Хранение журнала attack_events |
attack-monitor.known_bot_whitelist_source | enum(manual/user_agent_verified) | manual | нет | Как признаётся легитимный бот (поисковики и т.п.): вручную по IP или через верифицированный реверс-DNS/UA-паттерн |
attack-monitor.event_write_queue | string | attack-monitor | нет | Очередь для асинхронной записи событий — модуль не пишет в БД синхронно в горячем пути запроса |
attack-monitor.max_events_per_ip_per_minute | int | 120 | нет | Предохранитель: свыше этого числа однотипных событий с одного IP в минуту — агрегируются в одну запись со счётчиком, не по одной строке на событие |
attack-monitor.correlation_window_minutes | int | 30 | нет | Окно, в котором разные сигналы с одного IP/подсети (и события UserLoggedIn/UserPasswordChanged) считаются связанными для составного риск-скора |
attack-monitor.correlation_risk_threshold | int | 100 | нет | Составной риск-скор, при котором кросс-сигнальная корреляция форсирует эскалацию раньше одиночных порогов |
attack-monitor.blocklist_export_format | enum(caddy/fail2ban) | caddy | нет | Формат экспорта блок-листа: caddy — внутренний JSON для периметра студии, fail2ban — текстовый список IP для клиентских сайтов вне managed-парка без Caddy студии |
attack-monitor.emergency_disable_blocking | bool | false | нет | Kill-switch: переводит модуль в режим «только детект» — активная блокировка и эскалация на Caddy отключены, журнал и дашборд продолжают писать/показывать данные; для инцидента ложных срабатываний без потери видимости атак |
API
| Метод | Путь | Доступ | Назначение |
|---|---|---|---|
| GET | /api/v1/admin/attack-monitor/events | admin (attack-monitor.view) | Список событий с фильтрами |
| GET | /api/v1/admin/attack-monitor/blocks | admin (attack-monitor.view) | Активные блокировки |
| POST | /api/v1/admin/attack-monitor/whitelist | admin (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 по тому же IP | cms:attack-monitor:doctor --json, при подтверждении — принудительный сброс сессий скомпрометированной учётки (сервис ядра, вне модуля) |
События и обмен
| Событие | Когда | Payload |
|---|---|---|
AttackDetected | сработал детектор | ip, type, pattern, url |
AttackEscalated | эскалация на следующую ступень | ip, escalation_level |
AttackAlertThresholdExceeded | превышен порог событий/час | count, window |
AttackCorrelationDetected | успешный UserLoggedIn/UserPasswordChanged сразу после серии провалов с того же IP в окне correlation_window_minutes | ip, user_id, prior_failures, event_type |
AttackCompositeRiskEscalated | составной риск-скор кросс-сигнальной корреляции превысил correlation_risk_threshold | ip, 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-channel | out | алерт студии при превышении alert_threshold_per_hour; без модуля — только запись в журнал, без внешнего уведомления |
| Caddy (reverse proxy, внешний относительно CMS) | внутренний подписанный HTTP-эндпоинт /internal/attack-monitor/blocklist | out | периодический перечит блок-листа Caddy для бана на периметре — не входит в 5 внутренних каналов (внешний технический канал, аналог cms/webhooks-in но исходящий) |
| Студийная телеметрия парка | provides-контракт attack-detection | out | флит-дашборд агрегирует картину атак по всем 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 ядра — админ видит деградацию конкретно этой ступени, не общий «всё плохо»; - успешный вход сразу после серии провалов с того же IP →
AttackCorrelationDetectedне блокирует уже вошедшего (сессия штатно создана ядром), только поднимает приоритет расследования и алертит студию; принудительный логаут скомпрометированной учётки — вне зоны ответственности 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_eventsappend-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запрещены.