feat(mera/b2c): анти-абуз для анонимного трафика — этап 2 из 8 #2546
No reviewers
Labels
No labels
Fable 5 ревью
GG-форсайт
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
feedback/max
generative
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
ИРД
вторичка
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#2546
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/mera-b2c-antiabuse"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Второй этап плана запуска B2C. Наружу ничего не открывает — готовит защиту, без которой открывать эндпоинт оценки нельзя категорически. Мержится независимо от DNS.
Блокер, который закрывается
Анонимный запрос означал безлимит. В сервисе квот отсутствие имени пользователя трактовалось как unlimited во всех функциях, с комментарием «dev без Caddy, fail-open». Единственной защитой оставался общий лимит 300 запросов в минуту на IP — это анти-флуд для дешёвых запросов, а не бизнес-лимит для пайплайна, который работает десятки секунд и дёргает пять внешних сервисов.
Что сделано
Fail-open больше не по умолчанию. Пустая личность передаётся только при явно включённом флаге разработки, по умолчанию выключенном.
Анонимная личность — подписанная кука с HMAC-SHA256, отдельным каналом от
X-Authenticated-User. Это принципиально: тот заголовок ставит Caddy и валидирует внутренним секретом, смешивать схемы нельзя — сломалась бы модель безопасности.Квота на паре «сессия + IP», переиспользует существующую таблицу и тот же атомарный инкремент под
WHERE used < lim— уже проверенную защиту от гонки. По умолчанию 3 оценки в месяц на анонимную сессию. В коде честно оговорено: смена IP или чистка куки обходит лимит; задача — поднять стоимость злоупотребления, а не сделать его невозможным.Отдельный жёсткий лимит частоты на оценку — 5 запросов за 5 минут, проверяется до квоты, действует и на админов тоже, потому что это защита ёмкости сервера, а не бизнес-правило.
Потолок времени ответа. Вызов Avito IMV шёл без бюджета, в отличие от всех соседних — единственный источник неограниченного времени. Теперь обёрнут.
Складывается из геокодирования 12 с, метаданных дома 8 с, IMV 20 с, оценки Яндекса 8 с и ЦИАН 8 с — вызовы строго последовательные.
Осознанные решения
Redis не задействован. Прод работает одним воркером, состояние лимитера теряется только при рестарте, а основная защита — месячная квота в Postgres. Компромисс задокументирован в коде.
Ключ подписи куки берётся из окружения. Если не задан — эфемерный на процесс, с предупреждением в лог. ⚠️ Перед публичным запуском переменную надо задать на проде, иначе анонимные сессии будут сбрасываться при каждом рестарте вместе с учётом квоты.
Требует твоего решения
Лимит 3 бесплатные оценки в месяц на анонимную сессию — значение по умолчанию, выбранное мной. Для модели «краткая оценка бесплатно, подробный отчёт за деньги» это, возможно, стоит поднять или опустить. Меняется одной настройкой.
Test plan
uv run pytest -q— 2754 passed, 9 skippedruff checkс проектным конфигом — чистоРешение владельца 16.08.2026 и что осталось до мержа
Бесплатный лимит анонима — 5 расчётов в месяц (в первой редакции стояло 3). Значение выставлено дефолтом в
config.pyи переопределяется переменнойANON_ESTIMATE_QUOTA_LIMITбез выкатки кода. Обоснование зафиксировано комментарием рядом с настройкой: пять проб дают больше шансов, что человек дойдёт до ценности и купит платный отчёт, ценой чуть более высокого потолка злоупотребления.Ветка приведена к актуальному main, конфликты разведены композицией. Полный прогон бэкенда: 4609 passed, 1 skipped, 0 упавших (единственный skip — рендер PDF, нативных библиотек нет на машине прогона). После правки лимита
tests/test_account_quota.py— 62 passed,ruffчисто.Блокер, который остался ровно один
ANON_SESSION_SECRETна проде. Сейчас переменная пуста, и тогда секрет подписи анонимной куки генерируется заново при каждом старте процесса. Практическое следствие: любой деплой или перезапуск обнуляет все анонимные квоты — счётчик привязан к куке, а кука после смены секрета перестаёт проверяться. То есть анти-абуз будет работать ровно до ближайшей выкатки, а их у нас по несколько в день.Сгенерировать значение и положить в файл окружения бэкенда «Меры»:
Затем пересоздать контейнер — перезапуск не перечитывает окружение:
Проверка, что применилось: выдать анонимный расчёт, перезапустить контейнер, повторить расчёт с той же кукой — счётчик обязан продолжиться, а не начаться заново.
На что обратить внимание сразу после мержа
Вместе с квотой включается ограничитель частоты на
POST /estimate— 5 запросов за 5 минут, и он действует в том числе на админов и пилотов. Если планируются демонстрации с частыми расчётами подряд, это стоит проверить до показа, а не во время.