Перейти к основному содержимому

ADR 0091 — secops-runtime как скрипт отчёта по rate_limit_hits

  • Статус: Принят
  • Дата: 2026-08-16
  • Контекст: решённое предвосхищение роли secops-runtime (docs/fluxo.yml, camada_seguranca)

Контекст​

docs/fluxo.yml (ADR 0085) объявляет secops-runtime ролью proposto уровня безопасности, с papel_de_mercado: detecção e resposta и criterio_de_separacao: produção com tráfego real (pós DEPLOY_ENABLED + platform ativo). Этот триггер реален: автоматическое обнаружение инцидента, реагирование на инцидент и postmortem безопасности требуют непрерывного продакшен- трафика, чтобы иметь смысл, — тревога, откалиброванная против нулевого реального трафика, срабатывает на шум или никогда не срабатывает, а postmortem без инцидента — вымысел.

Владелец продукта решил предвосхитить часть роли, даже если триггер не сработал: RateLimitGuard (ADR 0027) уже записывает строку на каждый учтённый запрос в rate_limit_hits (apps/api/src/interfaces/http/shared/rate-limit.guard.ts), в том числе при трафике dev/CI, — реальные данные, собираемые сегодня, и без какого-либо отчёта по ним. apps/api/scripts/medir-execucao.ts (FASE 13b) уже установил шаблон для такого инструмента: NestFactory.createApplicationContext, чистое чтение через Drizzle, функции, извлечённые и протестированные без подъёма Nest.

Решение​

secops-runtime входит как скрипт — pnpm --filter api relatorio:seguranca-runtime, — а не как LLM-агент или GenServer. По данным нечего решать, только агрегировать: рейтинг корзин (bucket_key = user:<uuid> или ip:<endereço>, единственные два формата, которые записывает RateLimitGuard) по объёму обращений и временно́е распределение в фиксированных срезах для выявления пиков попыток.

Чего скрипт явно не делает, и почему:

  • Автоматическое обнаружение инцидента — потребовало бы порога, откалиброванного по реальному трафику; калибровка по трафику dev/CI дала бы число без связи со злоупотреблением в продакшене.
  • Реагирование на инцидент — нет реального инцидента, на который реагировать.
  • Postmortem безопасности — нет реального инцидента для расследования.

Все три зависят от ТОГО ЖЕ триггера, который docs/fluxo.yml уже объявлял (продакшен с реальным трафиком, после DEPLOY_ENABLED + активной platform), и остаются вне области. Отчёт перечисляет их в разделе "не измерено" — никогда не имитирует пример инцидента, никогда не выдумывает число обнаружения, — тот же принцип, что ADR 0041/0042/0077 уже применяют к другим возможностям, которых у продукта ещё нет: объявлять пробел, а не изображать его отсутствие.

Окно данных короткое, и отчёт это говорит: DomainGaugesCollector. pruneRateLimit удаляет обращения старше 2 × RATE_LIMIT_WINDOW_MS (240 с по умолчанию) каждые METRICS_GAUGE_INTERVAL_MS (15 с по умолчанию). Отчёт печатает НАСТРОЕННОЕ окно (теоретический предел очистки) и НАБЛЮДАЕМОЕ окно (то, что данные действительно покрывают), и никогда не позволяет второму выдать себя за историю больше, чем она есть, — если они совпадают, это сигнал, что более старые обращения были очищены, а не что их не было.

docs/fluxo.yml меняется с status: proposto на status: active в блоке secops-runtime, с entregaveis, заменяющими entregaveis_alvo: пункт deteccao становится реальным (via: script), а resposta-a-incidente/ postmortem-de-seguranca остаются как status: lacuna — поле не исчезает, а начинает указывать ровно на то, чего не хватает.

Последствия​

  • Реальный выигрыш: команда (pnpm --filter api relatorio:seguranca-runtime), которая уже сегодня показывает, кто больше всех упирается в rate limit и когда, не дожидаясь продакшена, — полезно даже в dev/CI, чтобы заметить шаблон некорректного теста или локального злоупотребления.
  • Отчёт хорош настолько, насколько хороши данные, которые хранит rate_limit_hits: без маршрута, метода или причины блокировки рейтинг не отличает "клиента, долбящего /auth/login" от "клиента, долбящего любой маршрут". Колонка маршрута потребовала бы изменить то, что записывает RateLimitGuard, — вне области этого среза, который лишь читает то, что уже существует.
  • Окно хранения (минуты, а не дни) ограничивает ценность рейтинга для уже прошедших инцидентов: запуск скрипта СЕГОДНЯ видит лишь то, что произошло за последние 2 × RATE_LIMIT_WINDOW_MS. Расширение хранения было бы отдельным продуктовым решением (растущая стоимость хранения в таблице без внешнего ключа, ADR 0027) и здесь не принято.
  • secops-runtime по-прежнему без автоматического обнаружения, реагирования на инцидент и postmortem — полное обещание рыночной роли всё ещё зависит от исходного триггера (DEPLOY_ENABLED + активная platform). Ничто в этом решении не приближает тот день; оно лишь не оставляет rate_limit_hits без какого-либо потребителя до тех пор.