6.2 KiB
Task 15 Report: Pre-auth лимит срабатывает ДО обращения к session store
Agent: Antigravity
Priority: MEDIUM (доступность / защита БД)
Date: 2026-08-21
Base SHA: 8e39ce0cbd809b9fb1666dce09905035bc493c10
Status: COMPLETED & VERIFIED
1. Проблема и воспроизведение дефекта
В реализации Task 12 проверка лимита для запросов с cookie (randomayzer_session=<value>) выполнялась после вызова getSessionFromRequest(req). Если атакующий отправлял пачку запросов с произвольными строками в cookie, каждый запрос выполнял SQL-запрос к prisma.session (или sessionStore.getSession()), нагружая базу данных, и лишь затем получал 429.
Воспроизведение на Base SHA (8e39ce0cbd809b9fb1666dce09905035bc493c10):
- 300 запросов без cookie:
getSessionCalls = 0, 60 ответов401, 240 ответов429(PASS). - 300 запросов с уникальными невалидными cookie:
getSessionCalls = 300, 60 ответов401, 240 ответов429(FAIL — все 300 запросов били в session store / БД).
2. Внесённые изменения
2.1. src/lib/rate-limiter.ts (SlidingWindowRateLimiter)
Добавлены методы для разделения проверки и списания квоты:
peek(key: string)— read-only инспекция скользящего окна. Возвращает{ allowed, remaining, resetInMs }без добавления timestamp и без модификации состояния.assertCanAttempt(key: string)— read-only проверка: выбрасываетRateLimitError(429), если квота клиента уже исчерпана, не изменяя счётчики.consume(key: string)— явное списание одного токена (вызовcheck(key)).assertAllowed(key: string)сохранён в неизменном виде для остальных вызывающих модулей в проекте.
2.2. src/lib/auth/auth-guard.ts (requireAuthenticatedUser)
Перестроен порядок выполнения шагов аутентификации и лимитирования:
- CSRF Guard (первым для POST/PUT/DELETE/PATCH).
- Pre-auth read-only check:
preAuthRateLimiter.assertCanAttempt(preAuthKey). Выполняется до любого обращения к session store или базе данных. Если квота попыток для данного IP исчерпана, запрос немедленно прерывается с429 RateLimitError(0 обращений к БД). - Проверка наличия cookie:
- Если cookie отсутствует:
preAuthRateLimiter.consume(preAuthKey)(списание попытки) и выброс401 UnauthorizedError(0 обращений к БД).
- Если cookie отсутствует:
- Обращение к session store:
getSessionFromRequest(req).- Если сессия не найдена / невалидна / истекла:
preAuthRateLimiter.consume(preAuthKey)(списание попытки) и выброс401 UnauthorizedError.
- Если сессия не найдена / невалидна / истекла:
- Валидная сессия: возврат
sessionUserбез списания токеновpreAuthRateLimiter.
3. Изменённые файлы
src/lib/rate-limiter.ts— добавлены методыpeek,assertCanAttempt,consume.src/lib/auth/auth-guard.ts— pre-authassertCanAttemptвынесен перед обращением кgetSessionFromRequest, списаниеconsumeтолько при failed auth.tests/pre-auth-rate-limit.test.ts— тесты обновлены для проверки 300 запросов, строгого ограничения обращений к session store (<= 60), и валидации ненарушения квоты активного пользователя.
4. Фактически выполненные проверки
-
Unit & Integration Tests (vitest):
npm testРезультат:
59 passed (59), 343 passed (343)- 300 запросов без cookie:
getSessionCalls === 0, 60x 401, 240x 429. - 300 запросов с уникальными невалидными cookie:
getSessionCalls === 60(<= 60), 60x 401, 240x 429. - 100 запросов активного аутентифицированного пользователя: 100x 200 OK,
preAuthRateLimiter.peekremaining = 60 (0 токенов pre-auth потрачено). - Все 8 защищённых эндпоинтов блокируют доступ до БД при исчерпании лимита.
- User-scoped изоляция сохранена.
- 300 запросов без cookie:
-
Linter:
npm run lintРезультат:
0 errors, 6 warnings(чисто, только стандартные next/image warnings). -
TypeScript typecheck:
npx tsc --noEmitРезультат:
exit 0(0 ошибок). -
Production build:
npm run buildРезультат:
Compiled successfully in 50s, static pages generated,exit 0.
5. Security & Invariant Check
- AGENTS.md §1 & §11: Алгоритмы жеребьёвки, доказательства, канонический хэш и VK-инварианты не изменялись.
- CSRF First: CSRF origin validation выполняется первым шагом.
- Quota & DB Protection: При флуде невалидными сессионными куками обращение к базе данных строго ограничено 60 вызовами за скользящее окно (1 минута).
- Legitimate Organizer Protection: Активные аутентифицированные пользователи не расходуют pre-auth токены и изолированы по
sessionUser.id.