# Спека — Go-Live Security Gate (пред-прод прогон безопасности)

## Цель

Один воспроизводимый прогон безопасности портала Лидерра перед выходом в интернет:
статический анализ, проверка персональных данных (152-ФЗ), модель угроз и
динамическое сканирование локальной копии — с единым собранным вердиктом
**GO / NO-GO** и письменным отчётом. Цель прогона — текущая ветка `main` на
локальной копии `127.0.0.1:8000`; боевой контур не трогается.

## Охват и гарды {#D0}

**Контракт.** Прогон — security-only: выдаёт GO/NO-GO только по безопасности и не
заменяет сквозной аудит портала (тесты/схема/UI/a11y/coverage). Все динамические
проверки направлены строго на локальную копию `127.0.0.1` — боевой адрес не
сканируется (гард IS8). Прогон не правит продуктовый код и не коммитит; единственная
запись — файл отчёта в `docs/security/`.

**Edge-cases.** Если инструмент не установлен (ZAP без Java, Ward/Nuclei отсутствуют) —
шаг фиксируется как `PENDING`, прогон продолжается, итог — «условный GO» с пометкой
обязательной установки до публичного деплоя. Если локальный сервер не поднят —
динамические шаги (Nuclei/ZAP) дают `PENDING`, а не ложный «чисто».

**Конвенция.** Severity → статус: `critical`/`high` любого инструмента = NO-GO;
`medium` = предупреждение (фиксируется, не блокирует); `low`/`info` = информационно.

**Критерий.** Прогон считается выполненным, когда все пять шагов отработали (или
отмечены `PENDING` с причиной) и собран отчёт с явным вердиктом GO/NO-GO,
перечнем блокеров, предупреждений и PENDING-инструментов.

## Шаг 1 — Статика {#D1}

**Контракт.** Последовательно: gitleaks (секреты в истории), Semgrep (статика кода),
Ward (конфиг/`.env`/зависимости/код Laravel). Каждый фиксирует результат в отчёте.

**Edge-cases.** gitleaks: учебная фикстура секрет-сканера — в allowlist gitleaks,
не считается утечкой. Рабочее дерево содержит незакоммиченные правки параллельной
сессии (карточка сделки F1/F2) — статика видит их; находки из незавершённого кода
помечаются как таковые.

**Конвенция.** gitleaks: любая реальная утечка = NO-GO (critical). Semgrep: 0
critical/high ожидаемо, medium — предупреждение. Ward: 0 critical; `APP_DEBUG=true`/
`APP_ENV=local` на dev-копии — ожидаемы (не боевой `.env`), фиксируются как info.

**Критерий.** Три инструмента отработали, их вывод разнесён по severity в отчёт.

## Шаг 2 — ПДн / 152-ФЗ {#D2}

**Контракт.** Навык `pdn-152fz-audit` в обоих режимах: Режим 1 (технический) — RLS на
таблицах ПДн, маскирование pg_anonymizer, отсутствие phone/email в логах и Sentry;
Режим 2 (соответствие) — хранение в РФ, согласия, права субъекта (`pd_subject_requests`),
журнал обработки (`pd_processing_log`), уведомление РКН.

**Edge-cases.** Известный долг F-P1 — `deals.phones[]` не вычищаются по сроку
хранения — проверяется и явно отражается в вердикте (см. {#D6}).

**Конвенция.** Нарушение Режима 1 уровня critical (ПДн в открытых логах/Sentry) =
NO-GO. Замечания Режима 2 — предупреждения, если не critical.

**Критерий.** Оба режима отработали, список нарушений (или «нет») в отчёте.

## Шаг 3 — Угрозы STRIDE {#D3}

**Контракт.** Навык `threat-model` — проверить, что топ-приоритетные STRIDE-угрозы
закрыты контрмерами: rate-limit на вход, HMAC на webhook, Sanctum token-auth, CSRF,
RLS. Известный долг F-T2 — `/api/admin/*` без app-гейта авторизации — проверяется
явно (см. {#D6}).

**Edge-cases.** Если актуальной модели угроз за последние 30 дней нет — модель
строится заново в этом прогоне.

**Конвенция.** Незакрытая топ-угроза = блокер либо принятый с владельцем риск;
середина — предупреждение.

**Критерий.** Перечень незакрытых топ-угроз (или «нет») в отчёте.

## Шаг 4 — Динамика {#D4}

**Контракт.** Цель — локальная копия `http://127.0.0.1:8000` (гард IS8). Nuclei —
широкое сканирование известных уязвимостей; ZAP — глубокое DAST (active scan).

**Edge-cases (обязательны).** Nuclei на native-Windows: цель строго `127.0.0.1`
(не `localhost` — резолвер пропустит), низкий темп `-rate-limit 20 -c 5` для
однопоточного `php artisan serve`. ZAP требует Java 17 + запущенного демона — при
отсутствии шаг = `PENDING`. Сервер должен быть поднят до сканирования.

**Конвенция.** critical/high из Nuclei/ZAP = NO-GO; medium — разбор вручную
(предупреждение).

**Критерий.** Динамические сканеры отработали по локальной цели (или `PENDING` с
причиной), находки — в отчёт по severity.

## Шаг 5 — Вердикт и отчёт {#D5}

**Контракт.** Собрать находки всех шагов в один отчёт `docs/security/` с заголовком,
датой, версией схемы, commit, целью и блоками по пяти шагам, завершаемый строкой
вердикта GO/NO-GO.

**Edge-cases.** Любой PENDING-инструмент → вердикт «условный GO» с явным перечнем
того, что обязано быть установлено до публичного деплоя.

**Конвенция.** Структура отчёта повторяет канон гейта: блоки ШАГ 1..4, затем
`=== ВЕРДИКТ ===` с тремя списками — блокеры (critical/high), предупреждения (medium),
PENDING-инструменты.

**Критерий.** Файл отчёта существует, содержит явный вердикт и три списка.

## Известные долги для явной проверки {#D6}

**Контракт.** Прогон обязан явно подтвердить или закрыть два interim-NO-GO из
предыдущего доменного аудита: F-T2 (`/api/admin/*` без app-гейта авторизации) и
F-P1 (152-ФЗ: `deals.phones[]` не вычищаются по сроку хранения).

**Edge-cases.** Если за время после аудита долг закрыт кодом — прогон это
подтверждает фактом (нашли гейт/очистку), а не «по памяти».

**Конвенция.** Открытый F-T2 или F-P1 = блокер вердикта, пока не закрыт или не
принят владельцем как осознанный риск.

**Критерий.** В отчёте по каждому из двух долгов — статус «закрыт фактом X» либо
«открыт, блокер».

```verified-context-json
[
  {"id":"vc1","kind":"EXTRACTED","ref":".claude/skills/security-go-live/references/gate.md","anchor":"IS8 — цель по умолчанию локальная"},
  {"id":"vc2","kind":"EXTRACTED","ref":"docs/Tooling_v8_3.md","anchor":"security-go-live — go-live security-gate"}
]
```
