Skip to main content
Назад к блогу
6 мин чтения

Я удалил rate limiting с контактной формы. Вот почему.

Rate limiting — рефлекс. На реальной портфолио-форме он ничего не решил, добавил зависимость от Redis и сломал легитимные отправки. Честная математика, когда его пропускать.

securitynext.jstradeoffs

Каждый туториал по контактной форме Next.js включает rate limiting. Большинство рекомендуют Upstash Redis со скользящим окном, 3 отправки в минуту на IP. Я отгрузил этот паттерн, прогнал месяц и удалил.

Это пост-компаньон к четырёхстрочному спам-фильтру, заменившему reCAPTCHA на этом сайте. Та же контактная форма. Ещё одно упрощение.

Что у меня было

Каноничный стек «безопасной контактной формы»:

Примерно 80 строк кода плюс Redis-зависимость плюс Cloudflare worker. Для контактной формы, получающей ~5 отправок в неделю.

Что я заметил через месяц

Три точки данных заставили пересмотреть.

1. Rate limiter ни разу не сработал на реальной атаке.

Honeypot и time-floor ловили каждую спам-попытку, которую я мог подтвердить. Я грепнул логи на «rate limit exceeded» — ноль за 31 день. Боты, пытавшиеся отправить, все фейлились на honeypot или time-floor до того, как доходили до rate limit. Rate limiter стоял после фильтров, которые реально работали.

2. Он сработал на двух легитимных пользователях.

Оба раза — люди в корпоративных сетях. Большой офис, общий IP, один человек отправляет за себя, другой — сразу после. Вторая отправка попала в лимит 3/мин. Они получили вежливый «попробуйте позже» и, вероятно, не попробовали.

Два потерянных лида на форме с ~20 отправками в месяц — это 10% ложных срабатываний на штуке, которая должна ловить ботов. Это хуже, чем не ограничивать.

3. Redis добавил режим отказа.

Upstash отличный. Но это ещё одна зависимость, ещё один API-вызов на отправку, ещё одно место, которое может тормозить или быть недоступным. Дважды за месяц ответ Upstash был >500ms, что сделало отправку ощутимо тормозной. Один раз (кратко) Upstash вернул ошибку, которую мой код обработал как fail open — rate limit не применился в ту минуту. На этом этапе: что он реально защищал?

Математика, когда rate limiting окупается

Rate limiting реально полезен, когда:

Rate limiting — рефлекс-без-окупаемости, когда:

Чем я заменил

Ничем. Цепочка фильтров теперь:

  1. Cloudflare на edge (поставил и забыл)
  2. Honeypot
  3. Time-floor (минимум 1500ms)
  4. Лимиты размера (name < 100, email < 200, message < 5000)
  5. Telegram-уведомление

Весь пайплайн. Нет Redis, нет состояния, нет скользящего окна. 60ms p95 время ответа.

Худший случай, в котором я могу ошибаться

Мотивированный атакующий мог бы, в теории, заскриптовать headless-браузер, который монтирует страницу, ждёт 1600ms, заполняет форму и отправляет — обходя time-floor. Теперь у меня бот, который отправляет 100 раз в минуту.

Что реально происходит тогда:

Ключевой инсайт: rate limiting — это фикс для конкретной формы атаки. Я могу добавить его обратно меньше чем за час, когда увижу атаку. Платить за него превентивно, навсегда, на форме, которой он никогда не был нужен — это налог на угрозу, которая не материализовалась.

Обобщённое правило

Раньше я добавлял каждый security-middleware из топового туториала. Теперь я задаю три вопроса перед добавлением:

  1. От какой конкретной атаки это защищает? Если не могу назвать одним предложением — защита не нужна.
  2. Во что обходится — в сложности кода, в зависимостях, в латенси, в ложных срабатываниях? Каждая защита — это налог.
  3. Могу ли я добавить это позже, когда атака реально появится? Для большинства защит на малотрафиковых системах ответ — да, и превентивная защита — перерасход.

Rate limiting на контактной форме провалил все три для меня:

  1. Атака, от которой он защищает, в основном ловится более дешёвыми upstream-фильтрами.
  2. Он добавил Redis, ~20 строк кода и 2 ложных срабатывания на 20 отправок.
  3. Я абсолютно могу добавить его обратно за час, когда (если) реальный атакующий появится.

Удалён. Не скучаю.

Оговорка

Это работает, потому что форма моя, объём маленький, и downstream-действие (Telegram-уведомление мне) не имеет публичных побочных эффектов. Если ты строишь:

— оставляй rate limiter. Он существует по причине, и причина — форма твоего конкретного случая.

Этот пост конкретно о малотрафиковых, owner-viewed, без побочных эффектов эндпоинтах форм. Интернет в основном пишет гайды «защита по умолчанию», что правильно для медианного проекта и неправильно для конкретных. Знать, когда «конкретный» применим к твоему проекту — это как ты перестаёшь платить за защиту, которая не нужна.

Честный трейд, который я делаю

Я меняю defence-in-depth против атаки, которая не случилась, на более простую кодовую базу с меньшим количеством зависимостей. Этот трейд правильный для этой формы сегодня. Он был бы неправильным для другой формы завтра.

Режим отказа, если я ошибаюсь: выходные спама в Telegram и пятнадцать минут git revert. Я переживу.

Режим отказа, если бы я оставил rate limiter: два потерянных реальных разговора в месяц, навсегда, молча. Не могу.


Если ты ревьюишь свой собственный security-стек и хочешь второе мнение, что оставить, а что удалить — я прочитаю твою модель угроз и отвечу одной страницей честной обратной связи. Не пытаюсь продать аудит — в половине случаев мой ответ «вы перетрачиваете». Контактная форма.