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-стек і хочеш друге мнение, що залишити, а що видалити — я прочитаю твою модель загроз і відповім однією сторінкою чесного зворотного звʼязку. Не намагаюся продати аудит — у половині випадків моя відповідь «ви перевитрачаєте». Контактна форма.