← Назад к проектам

Двадцать списаний по цене одного

Перед открытием доступа я прогнал по проекту собственный пентест: план на 15 фаз, каждая фаза — отдельный заход, с реестром находок и статусами. Главное решение там организационное: все проверки шли на изолированном экземпляре, где платные каналы были заглушены, а языковая модель подменена локальной — можно было бить по деньгам сколько угодно, не тратя реальных. Самые ценные находки оказались про параллельность. Списание звёзд по внутреннему каналу проверяло достаточность баланса суммой ДО того, как брало строки под блокировку, и после блокировки не перепроверяло — двадцать одновременных списаний по сто звёзд при балансе сто отвечали успехом каждому, а снято было за одно. Чинится переносом проверки на уже заблокированные строки; после фикса тот же залп даёт один успех и девятнадцать отказов, баланс — ноль.

Вторая того же рода: ход агента не был сериализован, и параллельные ходы читали один и тот же снимок баланса, сжигая токены у вендора по-настоящему, а списывалось за один — на замере вышло двадцатикратное расхождение. Обернул ход в ту же межпроцессную блокировку, что и тяжёлые генерации. Третья — не гонка, а экономика: жёсткий лимит по адресу на отправку кодов подтверждения пришлось снять, потому что российские операторы сидят за общим NAT и лимит бил по живым людям; без него перебор разных номеров с одного адреса означал неограниченный слив платного канала. Я проверил вживую — двадцать номеров с одного адреса, ни одного отказа — и закрыл это не адресным лимитом, а общим бюджетом на фактические отправки, который живёт в базе и потому общий для всех процессов и хостов.

Фактура

  • Двойное списание: PoC — 20 одновременных списаний по 100★ при балансе 100★ дают 20 успехов; после фикса 1 успех + 19 отказов, баланс 0.
  • Гонка хода агента: 6 одновременных ходов → 1 вызов модели вместо 6; до фикса замер показал двадцатикратный счёт у вендора при однократном списании.
  • Отказ подсчёта токенов работал «в пользу пользователя» и открывал полный потолок вывода мимо баланса — переведён в отказ «в пользу системы» с оценкой по объёму контекста.
  • Платные вызовы модели на создании и редактировании агента шли мимо общего гейта доступа и без потолка — около пяти бесплатных вызовов за сессию; закрыто гейтом и суточной квотой.
  • Бюджет отправки кодов: почему не по адресу — общий NAT у мобильных операторов; сделан скользящий потолок на фактические отправки, проверка стоит после поиска пользователя (перебор чужих номеров бюджет не жжёт) и до записи кода (при отказе ранее отправленный код жертвы остаётся рабочим).
  • Письма получили отдельную группу бюджета, чтобы флуд писем не съедал потолок платных SMS и наоборот.
  • Платёжный webhook при расхождении суммы раньше писал критический лог и всё равно начислял; теперь начисление отменяется, заказ замораживается, админам уходит разовое оповещение; сравнение в копейках, в ответ провайдеру всё равно код 200, чтобы он не ретраил детерминированное расхождение.
  • Бонус за акционную покупку выдаётся только за первую оплаченную единицу цикла — условным обновлением с проверкой числа затронутых строк; иначе можно было заранее наделать платёжных ссылок и оплатить их залпом, получив бонус с каждой. Возврат снимает и бонус пропорционально.
  • Регрессия по итогам: 78 тестов критических путей, плюс живые наборы по панели, сервисам и аналитике.
  • Статический анализ: 29 известных уязвимостей в зависимостях, три пропатчены заменой версий, две оставлены с обоснованием (одна — особенность дизайна неиспользуемого алгоритма, вторая — мёртвый импорт-фолбэк). Поиск секретов по всей истории (102 коммита на тот момент) — одно ложное срабатывание, утечек нет.