278a5509ee
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
130 lines
12 KiB
Markdown
130 lines
12 KiB
Markdown
=== 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` (E1–E3); 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).
|