Списки блокировок
Оператор настраивает источник: отображаемое имя, то, что идентифицируют его записи (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, что
утверждается модульным тестом: роль, способная решить судьбу совпадения, но не
способная прочитать клиента, решала бы вслепую.