Files
portal/docs/security/2026-06-17-go-live-security-report.md
T

130 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
=== SECURITY GO-LIVE REPORT ===
Дата: 2026-06-17
Версия схемы: v8.40
Commit: main @ ~cf813c10 (+ незакоммиченные правки параллельной сессии F1/F2 в рабочем дереве)
Цель: http://127.0.0.1:8000 (локальная копия, гард IS8)
> NB по процедуре: прогон выполнен под стеной «роутер-наставник». Импликации
> отражены ниже — часть статических инструментов не отработала по техническим
> причинам (установка/устаревшая команда/десинк указателя), а чтения для анализа
> ПДн/угроз получены через вставку файлов в чат (impl-режим стены блокирует Read).
> Это **частичный** прогон: динамика и анализ ПДн/угроз — выполнены; статический
> слой инструментов требует доустановки и перезапуска.
--- ШАГ 1: СТАТИКА ---
gitleaks: OK — чистый перезапуск 17.06: 2462 коммита (112.94 MB), **0 утечек**.
Учебная фикстура секрет-сканера — в allowlist. Секретов в истории нет.
Semgrep: OK — установлен (semgrep-1.166.0); `npm run sast` (конфиги p/php,
p/javascript, p/typescript, p/secrets + `.semgrep.yml`, флаг `--error`)
завершился **exit 0 → 0 находок** (`--error` дал бы exit 1 при любой
находке). Детальный вывод под стеной не читался, но exit-код определяет
результат: блокирующих находок Semgrep нет.
Ward: OK — перезапущен `./bin/ward.exe scan app/` (v0.4.1): 2 находки на
dev-`.env` — [High] APP_DEBUG включён, [Medium] APP_ENV=local;
config/dependency-scanner — 0. На dev ожидаемо; на боевом
`.env.production` обязательно APP_DEBUG=false / APP_ENV=production — проверить фактом.
Trail of Bits: SKIP — не применим к этому плановому прогону (вызывается вручную
перед первым публичным релизом / при крупных изменениях периметра).
--- ШАГ 2: ПДн / 152-ФЗ (анализ по чек-листу + код/схема) ---
Режим 1 (технический):
✅ RLS на `deals`/`users`/`auth_log`/`import_log`/`pd_processing_log` — включён
(schema v8.40, политики tenant_isolation). `supplier_leads`/`pd_subject_requests`
— SaaS-уровень без RLS осознанно (доступ через BYPASSRLS-воркер/админа).
`users.totp_secret` — шифруется `Crypt::encrypt` (не plain).
`PdAuditLogger->record(...)` пишет `pd_processing_log` при просмотре/создании
сделки (DealController::show/store) — журнал обработки работает на этих путях.
✅ DealController телефоны/ПДн в логи НЕ пишет; `activity_log.context` для
статуса/назначения — только id/slug.
⚠️ MEDIUM — логи: `config/logging.php` `LOG_LEVEL=debug`, PII-scrubbing
процессора в каналах нет. Риск утечки ПДн только если телефон попадёт в
текст исключения; Sentry-scrubbing (OPEN-И-16) — в `config/sentry.php`,
проверить регекс-маску фактически.
❌ HIGH — `pg_anonymizer` (OPEN-И-24) ставится только в фазе 3; на момент
прогона расширение может быть НЕ установлено → `pg_dump` отдаёт ПДн в
открытом виде. Проверить: `SELECT extname FROM pg_extension WHERE extname='anon';`
Режим 2 (соответствие):
✅ Хранение в РФ (Yandex Cloud ru-central1), `tenant_consents`, `pd_subject_requests`
с дедлайном 30 дней (триггер), `processing_restricted` (ст.21 ч.5),
append-only hash chain на `pd_processing_log` — присутствуют в схеме.
⚠️ Проверить вручную (вне кода): уведомление РКН, реестр обработки (ст.22.1),
договоры поручения с crm.bp-gr.ru / Unisender Go / JivoSite, локация
серверов Unisender Go.
--- ШАГ 3: УГРОЗЫ (STRIDE-артефакт, в коде этого прогона не переподтверждено) ---
Из модели угроз портала — незакрытые ТОП-приоритеты (P0/P1):
❌ P0 — `SAAS_ADMIN_TEST_BYPASS=true` в prod = полный доступ к /api/admin/** (E20/E21).
❌ P0 — SSRF через `POST /api/webhooks/test` (E13): нет фильтрации URL →
metadata YC 169.254.169.254 → IAM-токен.
❌ P0 — `GET /api/dashboard/summary` без auth (E18, MVP-заглушка).
❌ P0 — `GET /api/managers`, `GET /api/lead-statuses` без auth (E9) — раскрытие ФИО менеджеров.
⚠️ P1 — нет rate-limit на `login`/`forgot`/`2fa/verify` (E1E3); URL-secret
поставщика в access-логах (E4).
Baseline защиты (есть): RLS (40 политик), Sanctum SPA auth, per-token rate-limit
на webhook тенанта (prod), IP-allowlist на webhook поставщика, HMAC X-Webhook-Signature
(prod), auth_log/saas_admin_audit_log, bcrypt, signed URL отчётов.
--- ШАГ 4: ДИНАМИКА (локальная цель 127.0.0.1:8000) ---
Nuclei: OK — 916 шаблонов, 1 match severity INFO (`tech-detect:php`), 0 medium/high/critical.
Скан завершён за 7 мин. Info-детект стека — не блокер.
ZAP: PENDING — требует Java 17 + запущенного демона; не запускался этим прогоном.
→ установить/поднять ZAP до публичного деплоя (глубокий DAST/active scan).
--- ИЗВЕСТНЫЕ ДОЛГИ (явная проверка) ---
F-T2 (/api/admin/* без app-гейта): ЧАСТИЧНО ЗАКРЫТ фактом — по STRIDE-карте
группа `/api/admin/**` стоит за middleware `saas-admin` (fail-closed 503 в
prod). Остаточный риск переехал в P0 выше: `SAAS_ADMIN_TEST_BYPASS` + SSO
(Yandex 360) не реализован до Б-1. → подтвердить в `routes/api.php` +
`.env.production` (флаг false), затем снять.
F-P1 (152-ФЗ: deals.phones не вычищаются): ОТКРЫТ, подтверждён фактом — в модели
`Deal` только `SoftDeletes`, анонимизации/ретеншена телефонов нет; в схеме
у `deals.deleted_at` — просто soft-delete (у `users.deleted_at` комментарий
«soft delete + анонимизация», у `deals` — нет). `deals.phone`/`phones`/
`contact_name` после soft-delete остаются в открытом виде бессрочно.
→ реализовать retention-cron (hard-delete или анонимизация phone/contact_name
через N дней после deleted_at) + исполнение `request_type='deletion'`.
=== ВЕРДИКТ: NO-GO ❌ ===
Блокеры (critical/high):
- ПДн дампы: pg_anonymizer не установлен (HIGH) → pg_dump отдаёт ПДн открыто.
- F-P1 (HIGH, 152-ФЗ): нет ретеншена/анонимизации телефонов лидов после soft-delete.
- STRIDE P0 (из модели угроз, подтвердить в коде): SAAS_ADMIN_TEST_BYPASS в prod;
SSRF в /api/webhooks/test; открытые без auth /api/dashboard/summary, /api/managers,
/api/lead-statuses.
Предупреждения (medium):
- Логи без PII-scrubbing процессора (LOG_LEVEL=debug); проверить Sentry-маску фактически.
- Нет rate-limit на login/forgot/2fa/verify; URL-secret поставщика в access-логах.
PENDING / не отработали (должны быть закрыты до публичного деплоя):
- Semgrep #25: не установлен (нет на PATH) — установить и перезапустить `npm run sast`.
- Ward #70: устаревший флаг в плане — перезапустить `./bin/ward.exe scan app/`.
- gitleaks #8: перезапустить чисто (в этом прогоне пропущен из-за десинка стены).
- ZAP #68: требует Java-демона — установить и прогнать active scan.
Условный статус инструментов: даже при закрытии находок вердикт остаётся NO-GO до
доустановки Semgrep/ZAP и чистого перезапуска gitleaks/Ward — статический и
глубоко-динамический слои гейта на этой машине сейчас не полны.
=== END ===
## ПОПРАВКА — верификация по коду (17.06.2026)
Три пункта, ранее помеченные как P0 «из модели угроз», проверены по реальному коду и оказались закрытыми:
- E9 (`/api/managers`, `/api/lead-statuses`) + E18 (`/api/dashboard/summary`) — под `auth:sanctum`(+`tenant`), `app/routes/web.php`.
- E20 (`/api/admin/*`) — за middleware `saas-admin` (prod fail-closed 503).
- SSRF (`/api/webhooks/test`) — `WebhookUrlGuard` блокирует cloud-metadata/RFC1918/loopback (на сохранении и тесте), URL только `https://`.
**Вердикт ОСТАЁТСЯ NO-GO.** Реальные блокеры НЕ сняты: F-P1 (ретеншен телефонов лидов), pg_anonymizer (не установлен), прод-`.env` (APP_DEBUG/APP_ENV). Остаточные не-блокеры: bypass-флаг + SSO админки (config / Б-1), DNS-rebind на будущей доставке webhook (edge), rate-limit на login (P1 — проверить throttle).
---
## ДОБИВАНИЕ АУДИТА (17.06.2026) — pg_anonymizer + ZAP
**pg_anonymizer:** проверено фактом (`pg_extension WHERE extname='anon'` → пусто) — **НЕ установлен** (phase-3, OPEN-И-24). pg_dump отдаёт ПДн открыто → блокер B2 в силе.
**ZAP #68** (spider + passive, цель 127.0.0.1:8000, демон 2.17.0; active scan не гнали — гард IS8): spider завершён (status 100). Сводка alerts — **High 0**, Medium 4, Low 28, Informational 4. **0 high/critical → блокеров от ZAP нет.** 4 Medium = предупреждения (вероятно security-заголовки), разобрать.
**Аудит инструментами ЗАКРЫТ:** gitleaks 0 · Semgrep 0 · Ward 2(dev) · Nuclei 0(medium+) · ZAP 0(high).
**Вердикт остаётся NO-GO** — держат не сканеры, а: F-P1 (ретеншен телефонов), pg_anonymizer (не установлен), прод-`.env` (APP_DEBUG/APP_ENV).