# STRIDE — карта угроз портала Лидерра

Основан на реальных маршрутах `app/routes/web.php` (v8.26, 21.05.2026).
Стек: Laravel 13 + Vue 3 + PostgreSQL 16 RLS + Redis, Yandex Cloud `ru-central1`.

---

## Карта точек входа

| # | Точка входа | Маршрут(ы) | Аутентификация |
|---|---|---|---|
| E1 | Вход / регистрация | `POST /api/auth/login`, `POST /api/auth/register` | Публичный |
| E2 | 2FA и коды восстановления | `POST /api/auth/2fa/verify`, `POST /api/auth/2fa/recovery-use` | Публичный (pending-session) |
| E3 | Сброс пароля | `POST /api/auth/forgot`, `POST /api/auth/reset-password` | Публичный |
| E4 | Входящий webhook поставщика | `POST /api/webhook/supplier/{secret}` | URL-secret + IP-allowlist |
| E5 | Входящий webhook тенанта | `POST /api/webhook/{token}` | URL-token + (prod: HMAC X-Webhook-Signature + rate-limit) |
| E6 | API сделок | `GET/POST/PATCH/DELETE /api/deals`, `/api/deals/export`, `/api/deals/transition`, `/api/deals/restore` | Sanctum SPA + tenant |
| E7 | API проектов | `GET/POST/PATCH/DELETE /api/projects/{id}`, `/api/projects/bulk`, `/api/projects/{id}/sync` | Sanctum SPA + tenant |
| E8 | API импорта CSV | `POST /api/imports`, `GET /api/imports/{importLog}`, `/api/imports/unknown-statuses` | Sanctum SPA + tenant |
| E9 | Lookup-эндпоинты | `GET /api/managers`, `GET /api/lead-statuses` | **Без auth** (открытые) |
| E10 | Биллинг тенанта | `POST /api/billing/topup`, `GET /api/billing/wallet`, `/transactions`, `/invoices` | Sanctum SPA + tenant |
| E11 | Charges ledger | `GET /api/billing/charges`, `POST /api/billing/charges/export` | Sanctum SPA + tenant |
| E12 | API-ключи тенанта | `GET /api/api-keys`, `POST /api/api-keys/regenerate` | Sanctum SPA + tenant |
| E13 | Webhook-настройки тенанта | `GET/PUT /api/tenants/me/webhook-settings`, `POST /api/webhooks/test` | Sanctum SPA + tenant |
| E14 | Напоминания | `GET/POST/PATCH/DELETE /api/reminders/{id}` | Sanctum SPA + tenant |
| E15 | Уведомления | `GET/PATCH/POST/DELETE /api/notifications/{id}` | Sanctum SPA + tenant |
| E16 | Отчёты | `GET/POST/DELETE /api/reports/jobs/{id}`, `POST /{id}/retry`, `POST /{id}/cancel` | Sanctum SPA + tenant |
| E17 | Скачивание отчёта | `GET /api/reports/jobs/{id}/file` | Signed URL (без Sanctum) |
| E18 | Дашборд | `GET /api/dashboard/summary` | **Без auth** (MVP-заглушка) |
| E19 | Профиль / уведомления-настройки | `GET/PATCH /api/auth/me`, `PATCH /api/auth/me/notification-preferences` | Sanctum SPA |
| E20 | SaaS-admin: тенанты, биллинг, инциденты, система | `GET/PATCH /api/admin/**` | `saas-admin` middleware |
| E21 | SaaS-admin: импersonation | `POST /api/admin/impersonation/init`, `/verify`, `/end` | `saas-admin` middleware |
| E22 | SaaS-admin: supplier-integration | `GET/POST /api/admin/supplier-integration/**` | `saas-admin` middleware |
| E23 | 2FA setup (авторизованный) | `POST /api/2fa/init`, `/confirm`, `/disable`, `/regenerate-recovery-codes` | Sanctum SPA |
| E24 | SPA-оболочка | `GET /`, `/login`, `/register`, `/deals`, … (20+ маршрутов) | Без auth (Vue shell) |

---

## Дельта «закрытый круг → интернет»

До публикации портал доступен только команде (VPN или фиксированные IP).
После публикации **любой актор из интернета** может обратиться к каждому публичному
эндпоинту. Критические изменения:

| Изменение | Затронутые точки | Почему важно |
|---|---|---|
| Брутфорс и credential stuffing | E1 (login) | Нет rate-limit на `/api/auth/login` (на момент анализа) |
| Энумерация пользователей | E1, E3 | Разные ответы на «существующий / несуществующий email» создают oracle |
| Replay и forgery webhook | E4, E5 | Secret в URL виден в логах прокси/nginx; HMAC на E5 — «prod» (не в dev) |
| Открытые lookup-эндпоинты | E9 | `GET /api/managers`, `GET /api/lead-statuses` без auth — раскрывают ФИО менеджеров |
| Открытый дашборд | E18 | `GET /api/dashboard/summary` без auth — раскрывает KPI текущего тенанта |
| DoS на artisan-сервере | Все | `php artisan serve` не держит нагрузку; нужен nginx/Octane |
| SSRF через webhook-test | E13 | `POST /api/webhooks/test` отправляет запрос на URL из тела — риск SSRF во внутреннюю сеть YC |
| Impersonation без prod-auth | E21 | `saas-admin` middleware в dev-режиме пропускает без проверки (`SAAS_ADMIN_TEST_BYPASS`) |
| Signed URL без срока инвалидации | E17 | Отчёт с ПДн доступен 24 ч по ссылке без повторной аутентификации |

---

## STRIDE по точкам входа

### E1 — Вход / Регистрация (`POST /api/auth/login`, `POST /api/auth/register`)

| Угроза | Описание | Текущий контроль | Пробел |
|---|---|---|---|
| **S** Spoofing | Брутфорс пароля, credential stuffing | Bcrypt-хеш пароля | Нет rate-limit на login |
| **T** Tampering | Подмена `tenant_id` в теле запроса | `tenant_id` берётся из `auth()->user()`, не из тела | — |
| **R** Repudiation | Отрицание входа | `auth_log` пишет login/logout | Нет IP + User-Agent в каждой записи |
| **I** Info disclosure | Энумерация email через разные ответы | Unified-ответ на forgot (E3) | Login может раскрывать «нет такого пользователя» |
| **D** DoS | Флуд регистраций, засорение БД | — | Нет captcha / email-верификации на register |
| **E** Elevation | Регистрация с `is_admin=true` в теле | Mass-assignment guard (fillable) | Проверить `$fillable` в `User` — нет ли `role` |

### E2 — 2FA (`POST /api/auth/2fa/verify`, `POST /api/auth/2fa/recovery-use`)

| Угроза | Описание | Текущий контроль | Пробел |
|---|---|---|---|
| **S** Spoofing | Брутфорс 6-значного TOTP | TOTP 30-сек окно | Нет rate-limit на `/2fa/verify` |
| **T** Tampering | Подмена `pending_user_id` в session | Серверная session | Проверить изоляцию session между тенантами |
| **R** Repudiation | Использование кода восстановления | `auth_log` | Фиксируется ли `recovery_used` событие? |
| **I** Info disclosure | Тайминг-атака на сравнение TOTP | TOTP библиотека (constant-time?) | Проверить реализацию `verifyTwoFactor` |
| **D** DoS | Флуд на `/2fa/verify` истощает session-store | — | Нет rate-limit |
| **E** Elevation | Обход 2FA через `recovery-use` | Коды — одноразовые, хранятся hashed | Если коды в открытом виде — критично |

### E3 — Сброс пароля (`POST /api/auth/forgot`, `POST /api/auth/reset-password`)

| Угроза | Описание | Текущий контроль | Пробел |
|---|---|---|---|
| **S** Spoofing | Захват аккаунта через сброс пароля чужого email | Токен по email | Нет rate-limit на `/forgot` |
| **T** Tampering | Подмена токена сброса | Cryptographic token (Laravel default) | Проверить срок жизни токена (1 ч?) |
| **R** Repudiation | — | — | — |
| **I** Info disclosure | Энумерация email через тайминг ответа | Unified-ответ задокументирован в роутах | Проверить фактическую реализацию ответа |
| **D** DoS | Флуд `/forgot` → очередь email | — | Нет rate-limit → перегрузка Unisender Go |
| **E** Elevation | — | — | — |

### E4 — Webhook поставщика (`POST /api/webhook/supplier/{secret}`)

| Угроза | Описание | Текущий контроль | Пробел |
|---|---|---|---|
| **S** Spoofing | Подделка запроса от crm.bp-gr.ru | URL-secret + IP allowlist (`system_settings.supplier_ip_allowlist`) | Secret виден в логах nginx/прокси |
| **T** Tampering | Подмена payload (телефон, стоимость лида) | — | Нет HMAC на тело; только secret в URL |
| **R** Repudiation | Отрицание доставки лида | `supplier_leads.raw_payload` | Нет timestamp-подписи для доказательства |
| **I** Info disclosure | Secret в URL → в access-логах сервера | IP allowlist сужает круг | Ротация secret при компрометации? |
| **D** DoS | Флуд поддельных лидов → списание баланса | IP allowlist | Если allowlist обходится (SSRF) |
| **E** Elevation | Подмена `tenant_id` в payload | Берётся из `system_settings` глобально | Архитектурно корректно; проверить lookup |

### E5 — Webhook тенанта (`POST /api/webhook/{token}`)

| Угроза | Описание | Текущий контроль | Пробел |
|---|---|---|---|
| **S** Spoofing | Запрос от неавторизованного источника | URL-token из `tenants.webhook_token`; HMAC X-Webhook-Signature (prod) | HMAC только в prod; dev уязвим |
| **T** Tampering | Изменение payload в transit | HMAC-валидация (prod) | В dev отключена — нельзя тестировать на prod-данных |
| **R** Repudiation | — | `supplier_leads.raw_payload` | — |
| **I** Info disclosure | Token в URL виден в логах | Per-token rate-limit | Нет ротации token при смене API-ключа |
| **D** DoS | Replay flood | Per-token rate-limit (prod) | Нет в dev |
| **E** Elevation | Лид с завышенной ценой | Стоимость берётся из `PricingTierResolver`, не из payload | Архитектурно защищено |

### E9 — Открытые lookup-эндпоинты (`GET /api/managers`, `GET /api/lead-statuses`)

| Угроза | Описание | Текущий контроль | Пробел |
|---|---|---|---|
| **S** Spoofing | — | — | — |
| **T** Tampering | — | — | — |
| **R** Repudiation | — | — | — |
| **I** Info disclosure | ФИО менеджеров без аутентификации | — | **Нет auth** — любой из интернета получает список менеджеров |
| **D** DoS | Флуд запросами | — | Нет rate-limit |
| **E** Elevation | — | — | — |

### E18 — Дашборд без auth (`GET /api/dashboard/summary`)

| Угроза | Описание | Текущий контроль | Пробел |
|---|---|---|---|
| **I** Info disclosure | KPI, баланс, активность тенанта без аутентификации | — | **MVP-заглушка**: auth не включён; в prod обязателен |
| **D** DoS | Тяжёлый агрегационный запрос без auth | — | Доступен без токена |

### E20 — SaaS-admin (`GET/PATCH /api/admin/**`)

| Угроза | Описание | Текущий контроль | Пробел |
|---|---|---|---|
| **S** Spoofing | Доступ к admin-панели без Yandex 360 SSO | `saas-admin` middleware (fail-closed 503 в prod) | SSO не реализован до Б-1; `SAAS_ADMIN_TEST_BYPASS` в prod = полный доступ |
| **T** Tampering | Изменение тарифа, статуса тенанта без аудита | `saas_admin_audit_log` | — |
| **R** Repudiation | Отрицание действий admin | `saas_admin_audit_log` | Нет подписи/2FA для деструктивных операций |
| **I** Info disclosure | Данные всех тенантов | `saas-admin` middleware | SAAS_ADMIN_TEST_BYPASS=true в production = полный дамп |
| **D** DoS | Bulk-delete тенантов | — | Нет подтверждения для деструктивных bulk-операций |
| **E** Elevation | Impersonation любого тенанта | `saas-admin` middleware | Та же уязвимость через bypass |

### E21 — Impersonation (`POST /api/admin/impersonation/init`, `/verify`, `/end`)

| Угроза | Описание | Текущий контроль | Пробел |
|---|---|---|---|
| **S** Spoofing | Имперсонация без реального admin-права | `saas-admin` middleware | Bypass в dev/test режиме |
| **T** Tampering | Изменение `admin_user_id` в токене | Token-based flow | Проверить, что token не forgeble |
| **R** Repudiation | Отрицание сессии имперсонации | `impersonation_tokens` логирует | Нет нотификации целевому тенанту |
| **E** Elevation | Получение прав тенанта через impersonation | Scope ограничен tenant-контекстом | Если RLS bypass во время импersонации |

### E13 — SSRF через webhook-test (`POST /api/webhooks/test`)

| Угроза | Описание | Текущий контроль | Пробел |
|---|---|---|---|
| **T** Tampering | Отправка запроса на внутренний адрес YC | — | **Нет фильтрации URL** — SSRF во внутреннюю сеть Yandex Cloud (metadata service 169.254.169.254) |
| **I** Info disclosure | YC instance metadata (IAM-токен, настройки сети) | — | Критично: SSRF → metadata API → IAM credentials |

---

## Приоритизация рисков

Матрица: **Вероятность** (В — высокая / С — средняя / Н — низкая) ×
**Ущерб** (К — критический / В — высокий / С — средний / Н — низкий).

| Приоритет | Риск | Точка | Вероятность | Ущерб | Контрмера |
|---|---|---|---|---|---|
| 🔴 P0 | SAAS_ADMIN_TEST_BYPASS=true в prod | E20, E21 | В | К | Убедиться, что флаг false в `.env.production`; fail-closed middleware |
| 🔴 P0 | SSRF через `/api/webhooks/test` | E13 | С | К | Валидировать URL: запрещать RFC1918 + link-local + metadata-IP; использовать DNS-rebind защиту |
| 🔴 P0 | `GET /api/dashboard/summary` без auth | E18 | В | В | Добавить `auth:sanctum + tenant` middleware до prod |
| 🔴 P0 | `GET /api/managers`, `GET /api/lead-statuses` без auth | E9 | В | С | Добавить `auth:sanctum + tenant` |
| 🟠 P1 | Нет rate-limit на login / forgot / 2fa/verify | E1, E2, E3 | В | В | Laravel Throttle middleware (e.g. `throttle:5,1`) |
| 🟠 P1 | URL-secret поставщика виден в access-логах | E4 | С | В | Перевести на HMAC-заголовок; ротировать secret; закрыть логи |
| 🟠 P1 | Флуд поддельных лидов → списание баланса | E4, E5 | С | В | IP allowlist жёсткий; HMAC на тело (E4); idempotency-key |
| 🟡 P2 | Энумерация email на login (не только forgot) | E1 | В | С | Unified-ответ на login тоже |
| 🟡 P2 | Флуд регистраций без email-верификации | E1 | С | С | Email verification или captcha |
| 🟡 P2 | Signed URL отчёта 24 ч без аутентификации | E17 | Н | С | Сократить TTL; добавить revocation при logout |
| 🟡 P2 | Нет нотификации тенанту при impersonation | E21 | Н | С | Email/in-app уведомление при входе admin |
| 🟢 P3 | Тайминг-атака на TOTP | E2 | Н | С | Проверить constant-time compare в TwoFactorController |
| 🟢 P3 | Тайминг-атака на email в forgot | E3 | Н | Н | Unified-ответ + jitter sleep |

---

## Что уже защищает портал (baseline)

- **RLS PostgreSQL** — 39 политик; кросс-tenant утечка через SQL закрыта.
- **Sanctum SPA auth** — все бизнес-эндпоинты под `auth:sanctum + tenant`.
- **Per-token rate-limit** — на входящих webhook'ах тенанта (E5).
- **IP allowlist** — на webhook поставщика (E4).
- **HMAC X-Webhook-Signature** — на E5 в prod (не в dev).
- **`auth_log`** — фиксирует login/logout события.
- **`saas_admin_audit_log`** — фиксирует admin-действия.
- **Bcrypt** — хеш пароля; коды восстановления 2FA — hashed.
- **`saas-admin` middleware** — fail-closed 503 в prod (если `SAAS_ADMIN_TEST_BYPASS=false`).
- **Signed URL** — для скачивания отчётов (E17).
- **gitleaks** — pre-commit/pre-push; секреты не должны попасть в репозиторий.
