Skip to main content
Omniflow умеет подписываться на внешние списки блокировок и следить за несколькими метриками на предмет значений, которые выглядят неправильно. Обе поверхности порождают записи, которые читает оператор. Всю эту область определяет одно правило, и оно закреплено в схеме, в слое сервисов и в текстах панели: здесь ничто не наказывает клиента. Совпадение и сигнал — это свидетельство. Неблагоприятное действие, если оно вообще происходит, — это обычная мутация клиента или финансов на собственной странице клиента, со своим правом доступа, своей обязательной причиной и своим событием аудита. Именно это делает автоматическую детекцию безопасной для запуска вообще: худшее, что может сделать ложное срабатывание, — отнять у оператора время, которое нужно, чтобы его отклонить.

Списки блокировок

Оператор настраивает источник: отображаемое имя, то, что идентифицируют его записи (telegram_id, email или username), HTTPS-адрес, необязательный заголовок авторизации и интервал обновления.

Что хранится

Только дайджест. Каждая запись хранится как SHA-256 от нормализованного значения, посоленный видом идентификатора. Отсюда следуют три вещи. Omniflow не держит читаемой копии чужого списка, а значит, не может её утечь или переопубликовать. Отпечаток из одной установки ничего не значит в другой. И поиск по-прежнему остаётся точным обращением к индексу — единственным видом поиска, который нужен списку блокировок. Нормализация происходит до хеширования и зависит от вида. Идентификаторы Telegram приводятся к канонической десятичной форме, имена пользователей переводятся в нижний регистр с отбрасыванием ведущего @ или префикса t.me/, а адрес переводится в нижний регистр только в доменной части — приведение локальной части к нижнему регистру или удаление точек является соглашением конкретного провайдера, из-за которого два по-настоящему разных адреса столкнулись бы.

Обновление

Обновление заменяет записи источника целиком в рамках одной транзакции, поэтому запись, удалённая издателем, перестаёт совпадать в тот же миг, когда начинает действовать новый набор, а обновление, упавшее на середине, оставляет прежний список нетронутым, а не произвольный префикс нового. Принимаются и JSON-массив, и обычный текстовый файл с одним значением на строку, причём форма определяется по первому непробельному байту, а не по объявленному типу содержимого — список, отданный с небрежным text/plain, всё равно импортируется. Строка, которая не нормализуется, пропускается и учитывается в счётчике, а не роняет обновление: одна испорченная строка в чужом списке не должна оставить оператора с устаревшим списком блокировок и без объяснений. Источник, публикующий более 500 000 записей, отклоняется. Список, который вдруг вырос до такого размера, гораздо вероятнее неверно настроен или перенаправлен, чем действительно настолько велик, и его импорт заменил бы работающий список мусором.

Решение по совпадению

Совпадение фиксирует клиента, источник и вид идентификатора, который сработал. Оператор выносит решение allowed или blocked с обязательной причиной, и его личность записывается, поэтому неблагоприятное решение никогда не может быть анонимным. Повторное обнаружение совпадения, по которому уже принято решение, не открывает его заново. Список разрешений идёт дальше: клиент в списке разрешений пропускается проверкой целиком, и именно это позволяет решению оператора «с этим всё в порядке» пережить следующее обновление источника. Заблокированное совпадение можно обжаловать, и тогда оно возвращается в очередь на рассмотрение.

Детекция аномалий

Наблюдать можно за четырьмя метриками, у каждой своё правило: Правило несёт окно, порог предупреждения, порог тревоги и минимальную выборку. Пороги указываются в собственных единицах метрики — байты для трафика, минорные единицы для покупок и возвратов, простой счёт для рефералов, — потому что их пересчёт означал бы показ оператору числа, которого он не вводил. minimum_sample — это то, что не даёт совсем новой установке поднять тревогу на первых трёх заказах. Окно, в котором наблюдений меньше этого значения, не поднимает ничего.
Правило для трафика — проверка уровня, а не скорости. Трафиком владеет Remnawave, а Omniflow держит лишь счётчик, который наблюдал последним, поэтому правило отмечает подписку, чьё наблюдаемое потребление перешло порог, а не ту, чьё потребление быстро выросло. Окно правила управляет тем, как часто такая подписка поднимается повторно, а не тем, что измеряется.

Дедупликация

Состояние, сохраняющееся на протяжении нескольких прогонов оценки, — это один сигнал, а не по одному на прогон. Ключ дедупликации — это метрика, субъект и корзина окна, в которую попадает наблюдение, поэтому клиент, который держится выше порога шесть часов, порождает одну тревогу на окно, а не одну на проход. Сигнал, который оператор уже рассмотрел, остаётся рассмотренным, когда то же состояние наблюдается снова.

Свидетельства

Каждый сигнал несёт числа, которые его породили, — счётчики, суммы, валюту, окно — и никогда не несёт тела сообщения, ссылки, токена, адреса или любого другого содержимого клиента. Оператору, разбирающему сигнал, нужно видеть, почему тот сработал, а это арифметика, а не переписка.

Уведомления

Поднятый сигнал ставится в очередь в операторский топик anomaly в той же транзакции, что и сохраняет его, поэтому тревога не может быть объявлена по сигналу, который был откачен. Уведомление называет метрику, серьёзность и числа. Оно не называет клиента: операторская группа — это чат, а панель — то место, где обладатель права доступа найдёт субъект.

Когда всё это выполняется

Обе поверхности приводятся в движение internal/sweeper в процессе-воркере, а не запросом. Его проходы выполняются как периодические задачи River — lifecycle_sweep и anomaly_evaluation, — поэтому каждый выполняется один раз за интервал, сколько бы реплик воркера ни работало, и каждый идемпотентен, поэтому проход, выполнившийся дважды, приводит к тому же состоянию, что и выполнившийся один раз. См. Задачи и очереди. Оценка аномалий выполняется по собственному расписанию, потому что она агрегирует по таблице заказов, а сохраняющийся сигнал дедуплицируется по окну — более частый запуск не даёт дополнительных тревог, только дополнительную работу. Упавший проход никогда не останавливает остальные. Недоступность издателя не должна мешать истечению подарков, а правило, которое не удаётся оценить, не должно останавливать обновление списка. Каждый источник списка блокировок независим по той же причине: падение одного оставляет остальные обновлёнными и сохраняет прежние записи упавшего источника, вместо того чтобы опустошить список, который некому заменить.

Изменение порогов и источников

И то и другое редактируется в панели в разделе Риски, с правом risk.write. Форма порогов применяет то же правило согласованности, что и оценщик: порог тревоги не меньше порога предупреждения, а окно не короче пяти минут — поэтому правило, которое было бы молча пропущено во время оценки, отклоняется уже при сохранении. Каждое поле называет единицу, в которой оно задано. Форма источника принимает только HTTPS, а пустой заголовок авторизации означает «оставить сохранённый», поэтому повторное сохранение источника никогда не возвращает его учётные данные в браузер. Это верно, только пока URL не меняется: сохранение нового URL без повторного ввода заголовка отклоняется с 422 secret_required, потому что заголовок, введённый для одного издателя списка, никогда не должен уйти другому. Воркер скачивает источник только с публичного адреса. Loopback, частные, link-local (включая облачные метаданные) и прочие непубличные диапазоны отклоняются в момент подключения, после разрешения имени хоста, и повторно для каждого перенаправления, которое к тому же обязано оставаться на HTTPS. Отклонённый так источник помечается как сбойный с кодом ошибки destination_refused, отличным от unreachable, поэтому панель показывает, что проблема именно в адресе.

Права доступа

risk.read читает источники, совпадения, правила и сигналы. risk.write выносит решение по совпадению, обжалует его, управляет источниками и правилами и редактирует список разрешений. Каждая роль, обладающая risk.write, обладает и customers.read, что утверждается модульным тестом: роль, способная решить судьбу совпадения, но не способная прочитать клиента, решала бы вслепую.