Хардненинг по итогам пен-теста #25 (#56–#62) #63

Merged
NotBigGhost merged 7 commits from issue-56-62-hardening into dev 2026-09-13 18:38:11 +03:00
Collaborator

Закрывает находки пен-теста #25 (F1–F7). Одна ветка, по коммиту на находку.

Что сделано

Находка Задача Severity Правка
F1 #56 High Вход админа проходит через throttle (пара IP+логин, IP, аккаунт); неудачи — в аудит
F2 #57 High Отзыв JWT: token_version (смена/сброс пароля) + jti-denylist (logout); миграция 0013
F3 #58 High Caddy перезаписывает X-Forwarded-For реальным пиром; uvicorn --forwarded-allow-ips сужен
F4 #59 High production не стартует с дефолтными/слабыми SECRET_KEY/ADMIN_PASSWORD
F5 #60 Medium IP-независимый лимит на аккаунт; заметка про рестарт/воркеры/Redis
F6 #61 Low Схема API закрыта в проде; security-заголовки на edge (CSP подготовлена, выкл.)
F7 #62 Low Throttle регистрации по IP

Что проверено

  • pytest — 147 passed (было 131; +16 тестов на F1/F2/F4/F5/F6/F7).
  • alembic upgrade head на свежей БД → 0013, колонка token_version создаётся; путь идемпотентен (как 0003/0011). Данные dev/prod не трогались.
  • Тесты покрывают: throttle админ-входа (429), отзыв токена после logout и смены пароля + выживание другого устройства, сброс пароля админом рвёт сессии игрока, лимит на аккаунт при ротации IP, throttle регистрации, fail-fast конфига в проде, закрытие openapi.json/docs в проде.
  • Требует живой проверки на test-клоне (backend-тестами не покрывается): F3 (header_up + --forwarded-allow-ips — перебор с подменой XFF), F6 security-заголовки и включение CSP (не сломать SPA / Telegram-виджет / SSE). Caddyfile деплоится на VPS вручную.

Модель угроз (кратко; полная — в отчёте #25)

  • Активы: учётные записи игроков и администратора, JWT-сессии (fs_session 7 дней, fs_admin 8 ч), аудит.
  • Нарушители: неаутентифицированный внешний, вошедший игрок, владелец утёкшего токена, сетевой наблюдатель.
  • Закрываемые векторы: онлайн-перебор пароля админа (F1), бессрочность украденного токена (F2), снятие лимита и порча аудита подменой IP (F3), компрометация через дефолтные секреты (F4), распределённый перебор (F5).
  • Инварианты: «dev-код не в проде» сохранён (правки в общих модулях, dev-guard'ы не тронуты); контракт API не изменился → фронт не трогали (gen:api не требуется).

Замечания

  • Q1 (профиль/история любого игрока доступны любому вошедшему) — отдельной задачей не заводили (вероятно by design); в этот PR не входит.
  • F5: лимит на аккаунт намеренно щедрый (вектор lockout-DoS); вынос счётчиков в Redis — при переходе на несколько воркеров (сейчас --workers 1), вне этого прохода.

Closes #56
Closes #57
Closes #58
Closes #59
Closes #60
Closes #61
Closes #62

🤖 Generated with Claude Code

https://claude.ai/code/session_01XfTsytzT6TojfmprRDKiV6

Закрывает находки пен-теста #25 (F1–F7). Одна ветка, по коммиту на находку. ## Что сделано | Находка | Задача | Severity | Правка | |---|---|---|---| | F1 | #56 | High | Вход админа проходит через throttle (пара IP+логин, IP, аккаунт); неудачи — в аудит | | F2 | #57 | High | Отзыв JWT: `token_version` (смена/сброс пароля) + jti-denylist (logout); миграция `0013` | | F3 | #58 | High | Caddy перезаписывает `X-Forwarded-For` реальным пиром; uvicorn `--forwarded-allow-ips` сужен | | F4 | #59 | High | `production` не стартует с дефолтными/слабыми `SECRET_KEY`/`ADMIN_PASSWORD` | | F5 | #60 | Medium | IP-независимый лимит на аккаунт; заметка про рестарт/воркеры/Redis | | F6 | #61 | Low | Схема API закрыта в проде; security-заголовки на edge (CSP подготовлена, выкл.) | | F7 | #62 | Low | Throttle регистрации по IP | ## Что проверено - `pytest` — **147 passed** (было 131; +16 тестов на F1/F2/F4/F5/F6/F7). - `alembic upgrade head` на свежей БД → `0013`, колонка `token_version` создаётся; путь идемпотентен (как `0003`/`0011`). Данные dev/prod не трогались. - Тесты покрывают: throttle админ-входа (429), отзыв токена после logout и смены пароля + выживание другого устройства, сброс пароля админом рвёт сессии игрока, лимит на аккаунт при ротации IP, throttle регистрации, fail-fast конфига в проде, закрытие `openapi.json`/docs в проде. - **Требует живой проверки на test-клоне** (backend-тестами не покрывается): F3 (`header_up` + `--forwarded-allow-ips` — перебор с подменой XFF), F6 security-заголовки и включение CSP (не сломать SPA / Telegram-виджет / SSE). `Caddyfile` деплоится на VPS вручную. ## Модель угроз (кратко; полная — в отчёте #25) - **Активы:** учётные записи игроков и администратора, JWT-сессии (`fs_session` 7 дней, `fs_admin` 8 ч), аудит. - **Нарушители:** неаутентифицированный внешний, вошедший игрок, владелец утёкшего токена, сетевой наблюдатель. - **Закрываемые векторы:** онлайн-перебор пароля админа (F1), бессрочность украденного токена (F2), снятие лимита и порча аудита подменой IP (F3), компрометация через дефолтные секреты (F4), распределённый перебор (F5). - **Инварианты:** «dev-код не в проде» сохранён (правки в общих модулях, dev-guard'ы не тронуты); контракт API не изменился → фронт не трогали (`gen:api` не требуется). ## Замечания - **Q1** (профиль/история любого игрока доступны любому вошедшему) — отдельной задачей не заводили (вероятно by design); в этот PR не входит. - F5: лимит на аккаунт намеренно щедрый (вектор lockout-DoS); вынос счётчиков в Redis — при переходе на несколько воркеров (сейчас `--workers 1`), вне этого прохода. Closes #56 Closes #57 Closes #58 Closes #59 Closes #60 Closes #61 Closes #62 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01XfTsytzT6TojfmprRDKiV6
Agent added the Kind/Security label 2026-09-13 17:25:09 +03:00
Agent added 7 commits 2026-09-13 17:25:09 +03:00
logout отзывает предъявленный токен по jti (in-memory denylist до exp);
смена и сброс пароля инкрементят users.token_version (claim ver в JWT,
сверка в auth/deps) — все прежние сессии отзываются. Своё устройство при
смене пароля остаётся в сессии (перевыдача cookie). Миграция 0013. #57

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XfTsytzT6TojfmprRDKiV6
admin_login проходит через LoginThrottle (пара IP+логин, IP и сам аккаунт),
как вход игрока; исчерпание лимита -> 429. Неудачные попытки пишутся в аудит
(без пароля). Пароль админа — прямой путь к полному контролю, лимиты строже. #56

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XfTsytzT6TojfmprRDKiV6
Вход игрока и админа получают лимит login-user/admin-login-user, не зависящий
от IP: ротация X-Forwarded-For (#58) больше не снимает защиту полностью.
Успешный вход сбрасывает счётчики аккаунта. В docstring ratelimit — про сброс
при рестарте и необходимость внешнего стора при нескольких воркерах. #60

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XfTsytzT6TojfmprRDKiV6
throttle_register ограничивает частоту POST /api/auth/register с одного IP
через тот же LoginThrottle (считаются все попытки). Enumeration ников через
409 не закрываем — ники и так публичны в топе (отмечено в отчёте). #62

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XfTsytzT6TojfmprRDKiV6
model_validator в Settings: при APP_ENV=production приложение не стартует, если
SECRET_KEY дефолтный/короче 32 символов или (при включённом бутстрапе) ADMIN_PASSWORD
дефолтный/пустой. dev/test не затронуты — там дефолты остаются нормой. #59

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XfTsytzT6TojfmprRDKiV6
main.py: openapi.json/docs/redoc отдаются только в dev/test (нужны для gen:api),
в production отключены. Caddyfile (edge): HSTS, X-Content-Type-Options, X-Frame-Options,
Referrer-Policy, Permissions-Policy, скрыт Server; CSP подготовлена, но выключена до
проверки на test-клоне (строгая политика ломает SPA/Telegram-виджет/SSE). #61

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XfTsytzT6TojfmprRDKiV6
entrypoint.sh: --forwarded-allow-ips сужен с "*" до loopback + приватных сетей
compose (переопределяемо FORWARDED_ALLOW_IPS) — uvicorn сканирует XFF справа и
берёт реальный адрес, подставленное клиентом левое значение игнорируется.
Caddyfile: reverse_proxy перезаписывает X-Forwarded-For реальным пиром
(header_up {remote_host}) вместо добавления. Итог — достоверный IP для throttle
и аудита. Инфра-часть проверяется на test-клоне. #58

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XfTsytzT6TojfmprRDKiV6
NotBigGhost merged commit 37fe286db9 into dev 2026-09-13 18:38:11 +03:00
NotBigGhost deleted branch issue-56-62-hardening 2026-09-13 18:38:12 +03:00
Sign in to join this conversation.