Files
portal/docs/security/2026-06-17-go-live-security-report.md
T
Дмитрий 150f10c54a
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
docs(security): хвосты go-live аудита — снять дубли секций, статус блокеров B1/B4/B5, ward-report в .gitignore
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 18:25:59 +03:00

12 KiB
Raw Blame History

=== 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).