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без какого-либо потребителя до тех пор.