# Безопасность Compton MVP — не банк, но и не «admin/admin в prod». Ниже — как устроена защита и что проверить перед выкладкой. ## Auth: схема ```mermaid sequenceDiagram participant B as Браузер participant API as FastAPI participant RD as Redis participant PG as PostgreSQL B->>API: POST /auth/login API->>PG: bcrypt verify API->>RD: read auth_epoch API-->>B: access JWT (memory) + refresh cookie B->>API: GET /users/me + Bearer API->>RD: jti denied? epoch ok? alt revoked API-->>B: 401 TOKEN_REVOKED else ok API-->>B: 200 end B->>API: POST /auth/logout API->>RD: deny_jti + revoke refresh API-->>B: cookie cleared ``` ## Токены | Токен | Где живёт | Отзыв | |-------|-----------|-------| | Access JWT | память frontend | jti denylist + auth_epoch (Redis) | | Refresh | HttpOnly cookie | rotation + family reuse detection | | Email/reset | opaque → hash в БД | one-time, TTL 1ч | JWT claims: `sub`, `role`, `jti`, `auth_epoch`, `exp`. ### Мгновенный revoke `apps/api/app/core/jwt_denylist.py`: | Redis key | Смысл | |-----------|-------| | `jwt:deny:{jti}` | конкретный access-токен | | `auth:epoch:{user_id}` | версия сессий пользователя | | Событие | Действие | |---------|----------| | Logout | deny jti + revoke refresh | | Block | INCR epoch + revoke refresh | | Смена/reset пароля | INCR epoch + revoke refresh | **Production:** без Redis API не стартует. Redis упал — fail-closed (401, не «ну ладно»). **Dev:** in-memory fallback (не путать с prod). ## Криптография Всё через `apps/api/app/core/crypto.py`: | Данные | Метод | |--------|-------| | Пароли | bcrypt cost 12 | | Access JWT | HS256 | | Refresh/email tokens | SHA-256 + pepper | | Media URLs | HMAC-SHA256 + TTL | | Install secrets | `secrets.token_*`, generate-once + lock | ## Install secrets ```bash python apps/api/scripts/bootstrap_install.py # до первого docker compose up ``` | Файл | Содержимое | |------|------------| | `data/secrets/install.env` | PG, JWT, S3, MinIO | | `install.meta.json` | install ID, lock time | **Не ротировать** `POSTGRES_PASSWORD` / JWT после bootstrap без плана — иначе Postgres скажет фразу, которую вы уже видели, и будет прав. Reveal: Admin → Security → Install Secrets (superuser, аудит в `admin-audit.jsonl`). Восстановление: [deploy.md § восстановление](./deploy.md#восстановление-секретов). ## RBAC | Правило | Enforcement | |---------|-------------| | `/admin` → role=admin | Guard + API | | Security/Diagnostics/secrets → superuser | API + UI tabs | | Нельзя block/demote себя | admin service | | Last admin protected | admin service | | blocked/pending → 403 refresh | auth service | | forgot_password skip для blocked | auth service | | IDOR на профиль | users router | ## HTTP / инфра ### Nginx headers | Header | Значение | |--------|----------| | X-Frame-Options | DENY | | X-Content-Type-Options | nosniff | | Referrer-Policy | strict-origin-when-cross-origin | | HSTS | `default.tls.conf` (staging/prod) | ### Прочее - Origin/Referer на cookie-auth endpoints - Rate limit (prod: обязателен) - CMS: bleach, протоколы http/https/mailto - Avatar: jpeg/png/webp, re-encode, **SVG — нет** - CI: Bandit, pip-audit, gitleaks, npm audit - Postgres/Redis/MinIO — internal network - Firewall VPS: 22, 80, 443 ## Production guards `APP_ENV=production` → API **не стартует**, если: | Проблема | Env | |----------|-----| | Test routes | `ENABLE_TEST_ROUTES=true` | | OpenAPI | `ENABLE_DOCS=true` | | Rate limit off | `ENABLE_RATE_LIMIT=false` | | Cookie без Secure | `COOKIE_SECURE=false` | | Placeholder JWT | `change-me-*` | | Дефолтная БД | user:pass | | Без SSL mode | нет `sslmode=require` | | Без Redis | JWT revocation | ## Prod env (минимум) | Переменная | Значение | |------------|----------| | `APP_ENV` | production | | `ENABLE_DOCS` | false | | `ENABLE_TEST_ROUTES` | false | | `COOKIE_SECURE` | true | | `ENABLE_RATE_LIMIT` | true | | `EMAIL_DELIVERY_MODE` | smtp | | `SEED_DEMO_USERS` | false | | `DATABASE_URL` | …?sslmode=require | ## Чеклист ### Сделано в коде - [x] JWT в memory, refresh HttpOnly - [x] Anti-enumeration auth - [x] Origin/Referer validation - [x] RBAC + superuser - [x] CMS sanitization - [x] Nginx security headers - [x] CI security scans - [x] Install secrets bootstrap + lock - [x] JWT jti denylist + auth_epoch - [x] Admin audit log ### Проверить на staging - [ ] `/api/v1/docs` → 404 - [ ] HSTS за TLS - [ ] Dry-run recovery секретов - [ ] ZAP: 0 High/Critical → [release.md](./release.md) - [ ] k6 pass → [release.md](./release.md) - [ ] Lighthouse ≥ 85 на `/`