Skip to content

ТЗ — Антифрод (cms/antifraud)

Слой: 🟡 функц-модуль · Зрелость доноров: ★★ · Донор: catalog, er, freelance Статус: ТЗ к разработке

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

Детект накрутки отзывов, заявок и других сущностей: IP-кластеры, скорость действий, шаблонность текстов, fingerprint устройства. Скоринг сущности с карантином на модерацию и репутацией пользователя во времени.

  • Сигналы: IP-кластеры (много действий с одной подсети/IP), всплеск скорости (N событий за короткое время);
  • шаблонность текста (похожие/идентичные отзывы разных аккаунтов), fingerprint браузера/устройства;
  • скоринг сущности (отзыв, заявка, публикация) с указанием сработавших причин;
  • карантин на модерацию вместо автоудаления — решение остаётся за человеком;
  • репутация пользователя: накопительный показатель доверия, влияет на строгость порогов;
  • скоринг асинхронный по умолчанию — не блокирует синхронно горячий путь публикации;
  • поставляет агрегированные сигналы в cms/attack-monitor (общая картина аномалий парка).

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

requires: ядро · suggests: cms/moderation · provides: fraud-signal

Поведение при выключении: отзывы/заявки/публикации проходят без антифрод-скоринга — остаётся только премодерация модулей-потребителей (если включена), без автоматического выявления накрутки.

Стоимость внешних API: не применимо — скоринг считается по внутренним сигналам (IP-кластер, скорость, шаблонность текста, fingerprint), модуль не вызывает платных внешних сервисов сам.

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

ТаблицаКлючевые поляПримечание
cms_antifraud_scoresid, subject_type, subject_id, score, signals (json), statusрезультат скоринга, полиморфная привязка
cms_antifraud_user_reputationid, user_id, trust_score, updated_atнакопительная репутация пользователя
cms_antifraud_rulesid, signal_type, condition (json), weight, is_activeдекларативные правила детекта

Индекс на (subject_type, subject_id) в cms_antifraud_scores; signals — JSONB → GIN-индекс под фильтр по причинам. user_id в cms_antifraud_user_reputation — FK constrained()->index()->unique() (одна репутация на пользователя); status — PHP Enum. signal_type в cms_antifraud_rules — PHP Enum (ip_cluster, velocity, text_similarity, fingerprint, …); индекс на (signal_type, is_active).

Правила как декларативные условия, не код: сигналы (IP-кластер, скорость, шаблонность текста, fingerprint) конфигурируются как правила с весами в cms_antifraud_rules через Filament (по аналогии с cms_antispam_rules антиспама), а не хардкодятся в PHP; AntifraudService::score() на каждом скоринге читает активные правила из кеша группы и суммирует вес сработавших. Новый сигнал/порог — правка в Filament, деплой не требуется.

ПДн-паспорт (матрица v2.2): signals в cms_antifraud_scores может содержать текст сущности (отзыв/заявка) и fingerprint устройства — срок хранения привязан к жизни сущности-потребителя (subject_type/subject_id — полиморфная кросс-модульная ссылка без FK, каскад через БД невозможен). Ретеншн-джоба antifraud периодически удаляет записи, чей subject в таблице модуля-владельца больше не существует (orphan-очистка, не завязана на конкретное событие удаления каждого потребителя). trust_score в cms_antifraud_user_reputation хранится, пока у пользователя есть активность, либо до истечения простоя trust_score_decay_days; при UserDeleted (ревизия ядра 14.07.2026, п.3) запись репутации удаляется/анонимизируется немедленно — участие в каскаде «забыть по запросу» (152-ФЗ). «Выгрузить всё по субъекту» отдаёт текущий trust_score и историю его изменений, без сырых signals чужих сущностей.

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

Входы:

ИсточникДанные/поляЧем валидируется
Сервис-вызов модуля-потребителя (requires, например cms/reviews)subject_type, subject_id, actor (user_id/ip/fingerprint), text (опц.), created_atвызывающий модуль уже провалидировал бизнес-данные; антифрод принимает только объявленный контракт AntifraudService::score() — whitelist параметров
Filament: решение модератора по карантинуstatus (approve/reject)Policy antifraud.manage
cms:antifraud:rescoreпараметры пересчёта (диапазон дат/тип сущности)whitelist аргументов команды

Выходы:

ПотребительДанныеФормат
Модуль-потребитель (например cms/reviews)скор + сработавшие сигналы + вердиктсинхронный ответ сервиса (если включён синхронный режим) либо событие/статус после асинхронного скоринга
cms/attack-monitorагрегированные сигналы (без сырых ПДн)provides-контракт fraud-signal
Filament (карантин, карточка репутации)subject_type, subject_id, score, signals[]; user_id, trust_scoreтаблицы
события AntifraudQuarantined/AntifraudReputationChangedсм. «События и обмен»payload события

При отклонении/карантине вердикт несёт машиночитаемый код fraud_suspected (ревизия ядра 14.07.2026, п.1): модуль-потребитель, публикующий свой ответ через собственный публичный API, обязан прокинуть этот код в конверт {message, code, errors}, а не изобретать собственный.

Настройки (группа antifraud)

КлючТипДефолтaffectsPageCacheОписание
antifraud.scoring_enabledbooltrueнетKill-switch. false — все сущности проходят как «не проверено» (скоринг не считается, правила не читаются), без выключения модуля целиком
antifraud.quarantine_thresholdint60нетПорог скоринга для карантина
antifraud.ip_cluster_window_minutesint10нетОкно для детекта IP-кластеров
antifraud.trust_score_decay_daysint90нетСрок затухания репутации без активности
antifraud.sync_scoring_enabledboolfalseнетСкорить синхронно в момент вызова (иначе — асинхронно через очередь, вызывающий получает предварительный «не заблокировано»)
antifraud.sync_scoring_timeout_msint200нетТаймаут синхронного скоринга — превышение переводит вызов в асинхронный режим на этот запрос
antifraud.nat_ip_allowance_factorint3нетМножитель к порогу IP-кластера для подсетей, помеченных как офисные/NAT (снижает ложные срабатывания)

Все пороги и окна выше — лимиты и квоты модуля (матрица v2.2): явные дефолты, достижение — деградация (карантин/асинхронный fallback), не 500 и не тихое обрезание. antifraud.scoring_enabled — аварийная опция на случай ложных массовых блокировок из-за сбоя правил или порогов; переключается в Filament с подтверждением.

API

Отдельного API нет — админ-CRUD через Filament.

Компоненты

Filament: очередь карантина сущностей с указанием сработавших сигналов (объяснение вердикта — см. «Безопасность»), карточка репутации пользователя, редактор правил детекта (cms_antifraud_rules: тип сигнала, условие, вес, активность) — правка применяется к новым скорингам без деплоя. Команды: cms:antifraud:rescore --json (пересчёт по текущим правилам), cms:antifraud:import-legacy --source=<профиль> --json (перенос легаси-репутации, см. «Донорский код»).

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

СобытиеКогдаPayload
AntifraudQuarantinedсущность ушла в карантинsubject_type, subject_id, score, signals
AntifraudReputationChangedизменилась репутация пользователяuser_id, trust_score

Слушает: UserDeleted (ревизия ядра 14.07.2026, п.3) — удаляет/анонимизирует запись пользователя в cms_antifraud_user_reputation (каскад «забыть по запросу»). Скоринг сущностей при этом не подписан ни на одно чужое событие — вызывается только сервис-вызовом по requires от модулей- потребителей. Provides-контракт fraud-signal — агрегаты потребляет cms/attack-monitor без прямого обращения к чужим таблицам.

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

Сущность/модульКаналНаправлениеЧто происходит
cms/reviews / другой потребитель (requires со стороны потребителя)сервис-вызов AntifraudService::score()inпотребитель просит скор перед публикацией сущности; ответ синхронный или предварительный (см. sync_scoring_enabled)
Очередь antifraudджоба асинхронного скорингаin→outпри асинхронном режиме сущность публикуется предварительно, окончательный вердикт приходит после — потребитель обязан уметь «откатить» публикацию в карантин по событию AntifraudQuarantined
cms/attack-monitorprovides-контракт fraud-signaloutагрегаты аномалий (без сырых ПДн) доступны детектору для общей картины атак парка
cms/moderation (suggests)сервис-вызов (если установлен)outкарантинная сущность может попадать в общую очередь модерации вместо отдельного экрана
Ядро — жизненный цикл пользователясобытие UserDeletedinкаскад «забыть по запросу»: репутация пользователя удаляется/анонимизируется

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

Именованная очередь antifraud для пересчёта скоринга (cms:antifraud:rescore), для затухания репутации по trust_score_decay_days и для ретеншн-очистки cms_antifraud_scores от записей с удалённым в модуле-владельце subject (ПДн-паспорт — см. «Модель данных»); джобы идемпотентны. Основной режим скоринга — асинхронный (см. «Безопасность» и «Крайние случаи»): синхронный вызов допускается только при sync_scoring_enabled=true и обязан укладываться в sync_scoring_timeout_ms.

Ранбук (типовые инциденты):

СимптомЧто проверитьКоманда
Волна ложных блокировок с одной подсети (офис/NAT)Не помечена ли подсеть как офисная — nat_ip_allowance_factor действует только на помеченные подсетиПометить подсеть NAT в Filament, проверить/поднять nat_ip_allowance_factor
Резкий всплеск карантина по всему сайтуКорреляция по времени с инцидентом в cms/attack-monitor (атака/бот-волна, а не просто агрессивные правила)Карточка инцидента cms/attack-monitor; при подтверждении рассинхрона правил — cms:antifraud:rescore --json после правки весов
Репутация не пересчитывается / trust_score не затухаетДжоба затухания по trust_score_decay_days зарегистрирована в ScheduleRegistrar, очередь antifraud не отстаётcms:antifraud:doctor --json, вручную cms:antifraud:rescore --json

Бэкап/рестор: в бэкап попадают cms_antifraud_scores, cms_antifraud_user_reputation, cms_antifraud_rules. Денормализованных агрегатов модуль не хранит — отдельной команды-восстановителя после рестора не требуется; при подозрении на рассинхрон допустим rescore по диапазону дат, но это не обязательный шаг завершения рестора.

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

  • Ожидаемый объём: сотни–тысячи скорингов в сутки на активный сайт с отзывами/заявками; IP-кластерный анализ работает с окном в минуты, не с полной историей — не растёт линейно с возрастом сайта;
  • горячий путь публикации сущности потребителем — бюджет 0 синхронных запросов к antifraud по умолчанию (sync_scoring_enabled=false): скоринг уходит в очередь, сущность публикуется как «на модерации» или «предварительно опубликована» по правилам потребителя; при включённом синхронном режиме — бюджет ≤2 запроса и обязательный таймаут с fallback на асинхронный путь;
  • индекс (subject_type, subject_id) критичен для карантина; для IP-кластерного детектора нужен индекс по (ip, created_at) в исходных данных потребителя (не в таблицах antifraud — сигнал считается из данных, переданных сервис-вызовом);
  • кешируется: antifraud:reputation:<user_id> — репутация пользователя, инвалидируется событием AntifraudReputationChanged; пороговые настройки — из кеша группы (0 запросов на горячем пути скоринга);
  • батч-пересчёт (rescore) — чанками (Bus::batch), не единой транзакцией на весь объём — иначе долгая блокировка при большом числе сущностей.

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

Скоринг вызывается только сервис-вызовом модулей-потребителей по requires, не через прямой HTTP. Сигналы в карантине читаемы и не являются «чёрным ящиком» — причины хранятся структурированно в signals. В логи и агрегаты не попадают ПДн сверх необходимого. Векторы, специфичные для модуля:

  • скоринг не должен блокировать синхронно горячий путь — публикация отзыва/заявки не ждёт результата антифрода по умолчанию; синхронный режим — осознанная опция с таймаутом и деградацией к асинхронному при превышении sync_scoring_timeout_ms;
  • ложное срабатывание на NAT/офисной сети — множитель nat_ip_allowance_factor для помеченных подсетей снижает вес IP-кластерного сигнала; помечать подсети — вручную в Filament (whitelist для конкретного клиента, не автоопределение);
  • VPN/прокси — детект IP-кластера не единственный сигнал: комбинация с fingerprint устройства и шаблонностью текста снижает вероятность бана честного пользователя за общим VPN-выходом;
  • отравление репутацииtrust_score меняется только через события модуля, прямая правка в БД/API отсутствует; ручная корректировка — только Filament с правом antifraud.manage-rules и записью в аудит;
  • объяснимость вердикта (explainability) — принцип, не опциональная деталь UI: ответ AntifraudService::score() и карточка карантина в Filament обязаны показывать не только итоговый score, но и перечень сработавших правил cms_antifraud_rules с их весами и вкладом в сумму — модератор и вызывающий модуль видят «почему», а не только число; сигналы не «чёрный ящик».

Права: antifraud.view, antifraud.manage, antifraud.manage-rules (studio).

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

ДействиеМенеджер (antifraud.view)Модератор (antifraud.manage)Studio (antifraud.manage-rules)
Просмотр карантина и карточки репутации
Решение по карантину (одобрить/отклонить, в стоп-лист)
Пометка подсети NAT/офис
Правка правил детекта (cms_antifraud_rules: сигнал, условие, вес)
Ручная корректировка trust_score пользователя
Kill-switch antifraud.scoring_enabled

Решения по конкретным сущностям (карантин, NAT-подсети) — уровень модератора сайта; правила детекта и веса меняют скоринг сразу для всего парка сигналов, ручная правка репутации и аварийное отключение скоринга — операции повышенного риска, доступны только studio-роли.

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

Админ:

  • пустая очередь карантина — «подозрительных сущностей не обнаружено»;
  • массовые действия: «одобрить выбранные», «карантин → в стоп-лист автора»;
  • карточка репутации показывает историю изменений trust_score, не только текущее значение — модератору нужен контекст, почему пользователь «токсичен»;
  • ошибка вызова antifraud потребителем (таймаут, недоступность очереди) — «сущность опубликована без проверки на накрутку, будет пересчитана автоматически», не тихий провал;
  • пометка подсети как NAT/офисной — с подтверждением и объяснением эффекта (снижает чувствительность детектора для этой подсети);
  • переключение kill-switch antifraud.scoring_enabled — с подтверждением и явным объяснением эффекта («все сущности будут проходить как непроверенные, карантин новых записей не создаётся; накопленный карантин не трогается»).

Посетитель:

  • пользователь, чья заявка/отзыв ушли в карантин, не должен видеть признаков бана — публикация асинхронная, задержка воспринимается как обычная модерация, не как кара;
  • нет отдельного UX для антифрода на публичной стороне — вся логика скрыта за потребляющим модулем.

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

  • ложное срабатывание на NAT/офисной сетиnat_ip_allowance_factor повышает порог IP-кластера для помеченных подсетей; без пометки — комбинация сигналов (IP + fingerprint + шаблонность текста), не один IP-кластер в одиночку;
  • VPN/прокси у честного пользователя → скоринг не блокирует синхронно, уходит в карантин на решение модератора, а не в автоблокировку — человек разбирает пограничные случаи;
  • скоринг блокирует горячий путь → ⚠️ Противоречие: донорский код (catalog, er) исторически считал скор синхронно при сохранении отзыва. Разрешение: в CMS v2 дефолт — асинхронный скоринг (sync_scoring_enabled=false), синхронный — только явная настройка с таймаутом; донорский синхронный путь переносится как основа асинхронной джобы, не как есть;
  • гонка: два одновременных действия одного пользователя (двойной сабмит отзыва) → скоринг идемпотентен по (subject_type, subject_id) — повторный вызов на ту же сущность не создаёт вторую запись в cms_antifraud_scores (upsert по уникальному индексу);
  • параллельный rescore и новое событие AntifraudReputationChanged → джоба батча берёт снимок на момент старта, конкурентное изменение репутации применяется следующим прогоном — не теряется и не дублируется (идемпотентность §9 стандарта);
  • выключение модуля посреди накопленного карантина → карантинные записи остаются в БД, но новые вызовы AntifraudService::score() от потребителей должны получать graceful fallback («не заблокировано, антифрод недоступен»), не исключение;
  • cms/moderation не установлен (suggests отсутствует) → карантин работает через собственный Filament-экран модуля, не через общую очередь модерации — деградация предусмотрена, не поломка;
  • пустой fingerprint/IP (заявка через API без стандартных заголовков) → сигналы, требующие IP/fingerprint, просто не участвуют в скоринге — сумма по доступным сигналам, не отказ в скоринге целиком;
  • огромный объём сущностей при первом включении на существующем сайтеrescore обязан идти чанками (Bus::batch) с видимым прогрессом в админке, не одной долгой транзакцией — иначе таймаут команды/память;
  • репутация не пересчитывается годами (нет активности пользователя) → trust_score_decay_days затухает по расписанию, а не только по событию — джоба планировщика, не завязана на визит пользователя;
  • kill-switch antifraud.scoring_enabled=false → новые вызовы AntifraudService::score() возвращают «не проверено» без обращения к правилам и без постановки в очередь antifraud; уже накопленный карантин не разбирается автоматически — ждёт модератора как обычно, повторное включение не теряет историю;
  • повторный прогон cms:antifraud:import-legacy → идемпотентен по user_id/ external_id: обновляет существующую репутацию, не создаёт дубль; --dry-run только отчитывается о расхождениях, в БД не пишет;
  • UserDeleted для пользователя с активной репутациейtrust_score удаляется/ анонимизируется каскадно; уже вынесенные вердикты по его прошлым сущностям (cms_antifraud_scores) задним числом не пересматриваются.

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

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

Легаси-импорт репутации (§16 стандарта): cms:antifraud:import-legacy --source=<профиль> [--dry-run] --json переносит накопленный trust_score пользователей с донорской площадки (catalog, er, freelance) при миграции клиента — маппинг по user_id/external_id, идемпотентен (повторный прогон обновляет запись, не дублирует), --dry-run отдаёт отчёт расхождений без записи в БД. Сырые signals/исторические причины бана донора не переносятся — только агрегированный trust_score, чтобы не тащить непроверенные основания в новую систему правил.

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

  • [ ] Контрактный тест: серия однотипных отзывов с одной подсети за окно ip_cluster_window_minutes уходит в карантин;
  • [ ] Карантин не удаляет и не публикует сущность до решения модератора;
  • [ ] Репутация пользователя снижает/повышает эффективный порог скоринга (доверенные — мягче);
  • [ ] Сигналы в карантине читаемы и объясняют причину (не «чёрный ящик»);
  • [ ] Деградация при выключении не блокирует публикацию отзывов/заявок;
  • [ ] Агрегаты сигналов доступны cms/attack-monitor без прямого обращения к чужим таблицам;
  • [ ] По умолчанию скоринг асинхронный — публикация сущности не ждёт результата antifraud;
  • [ ] Синхронный режим соблюдает sync_scoring_timeout_ms и деградирует к асинхронному при превышении;
  • [ ] Подсеть, помеченная NAT/офисной, не даёт ложного карантина при обычной активности нескольких пользователей;
  • [ ] rescore идёт чанками с прогрессом, не одной транзакцией на весь объём;
  • [ ] Правило детекта редактируется в Filament без деплоя и применяется к новым скорингам;
  • [ ] Вердикт содержит человеко-читаемое объяснение (список сработавших правил с весами), не только итоговый score;
  • [ ] antifraud.scoring_enabled=false отключает скоринг целиком (все сущности — «не проверено»), не выключая модуль;
  • [ ] cms:antifraud:import-legacy идемпотентен: повторный прогон не дублирует репутацию, --dry-run не пишет в БД;
  • [ ] UserDeleted удаляет/анонимизирует cms_antifraud_user_reputation, не затрагивая прошлые cms_antifraud_scores;
  • [ ] При отклонении/карантине ответ сервиса несёт код fraud_suspected для проброса в API вызывающего модуля;
  • [ ] Контрактный набор cms-testing зелёный, testbench-изоляция пакета, feature-тест на каждый роут;
  • [ ] Тестовая БД только antifraud_test; migrate:fresh/refresh/reset, db:wipe запрещены.

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