docs(security): go-live security gate отчёт 17.06 + уроки прогона в wall-guide

Прогон security-go-live на main, локальная цель 127.0.0.1:8000 — вердикт NO-GO.
Блокеры: pg_anonymizer не установлен (ПДн в дампах), F-P1 (телефоны лидов не
вычищаются по сроку), P0 из STRIDE (SAAS_ADMIN_TEST_BYPASS / SSRF webhooks-test /
открытые ручки). Nuclei чисто (1 info php). Semgrep/ZAP — PENDING.

Гайд стены: новый раздел уроков — читать контекст до печати плана, запасной
канал вставки в чат, недетерминизм судьи и рассинхрон указателя F-J.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-06-17 14:34:39 +03:00
parent 2a6b476d6d
commit e693cfc6b7
10 changed files with 714 additions and 0 deletions
@@ -0,0 +1,109 @@
=== 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: SKIP (не перезапущен в этом прогоне — десинк указателя стены F-J).
Известный результат за сегодня (сессия 2026-06-17): история чиста,
единственная находка — учебная фикстура секрет-сканера в allowlist.
→ требуется чистый перезапуск `./bin/gitleaks.exe detect --source . --log-opts "--all"`.
Semgrep: PENDING — `semgrep` не установлен / не на PATH в этой оболочке
(`'semgrep' is not recognized`). Команда `npm run sast` падает на запуске бинаря.
→ установить Semgrep (pip/pipx) до публичного деплоя.
Ward: ERROR — в плане использован устаревший флаг `--path` (актуально
позиционно: `ward scan app/`). Бинарь установлен (`bin/ward.exe`),
но скан не выполнился. → перезапустить `./bin/ward.exe scan app/`.
(По памяти прошлого smoke Ward давал High `APP_DEBUG`, Medium `APP_ENV=local`
— для dev-`.env` ожидаемо; на боевом `.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 ===
@@ -0,0 +1,70 @@
# План v2 — Go-Live Security Gate (пред-прод прогон безопасности)
## Цель
Прогнать пять шагов гейта безопасности по спеке (статика → ПДн/152-ФЗ → угрозы →
динамика → вердикт) на текущей ветке `main`, цель — локальная копия
`127.0.0.1:8000`, и собрать письменный отчёт с вердиктом GO / NO-GO. Прогон не
правит продуктовый код, схему и не коммитит; единственная запись — файл отчёта.
```skills-json
["security-go-live", "pdn-152fz-audit", "threat-model"]
```
```steps-json
[
{"op":"Bash","object":"./bin/gitleaks.exe detect --source . --log-opts \"--all\"","ref":"D1"},
{"op":"Bash","object":"npm run sast","ref":"D1"},
{"op":"Bash","object":"./bin/ward.exe scan --path app/","ref":"D1"},
{"op":"Bash","object":"php app/artisan serve --host 127.0.0.1 --port 8000 &","ref":"D4"},
{"op":"Bash","object":"bin/nuclei.exe -u http://127.0.0.1:8000 -tags tech -rate-limit 20 -c 5 -timeout 5 -duc","ref":"D4"},
{"op":"Bash","object":"bin/nuclei.exe -u http://127.0.0.1:8000 -rate-limit 20 -c 5 -timeout 5 -duc -severity medium,high,critical","ref":"D4"},
{"op":"Write","object":"docs/security/2026-06-17-go-live-security-report.md","ref":"D5"}
]
```
```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"}
]
```
## Переговоры
### Круг 1
Доводы по составу шагов:
- **Статика (gitleaks/Semgrep/Ward)** — read-only по локальному репозиторию и
`app/`, ничего не правит и не отправляет наружу. gitleaks читает git-историю,
Semgrep (`npm run sast`) и Ward (`./bin/ward.exe scan`) читают код и конфиг.
Это безопасные локальные проверки.
- **ПДн/152-ФЗ и угрозы** вынесены в навыки `pdn-152fz-audit` и `threat-model`
(объявлены в skills-json) — они анализируют схему и код только чтением; их
находки попадают в отчёт.
- **Динамика (Nuclei)** направлена строго на локальную копию `http://127.0.0.1:8000`
(гард IS8 — боевой контур не трогается). Темп ограничен `-rate-limit 20 -c 5`
под однопоточный `php artisan serve`, чтобы не перегрузить dev-сервер. Цель —
`127.0.0.1`, не `localhost` (резолвер Nuclei на этой машине пропускает `localhost`).
Сначала быстрый smoke по тегу `tech`, затем полный прогон severity medium+.
- **`php artisan serve`** нужен, чтобы портал был доступен сканеру; запускается
фоном на локальном адресе, продуктовых данных и схемы не меняет.
- **ZAP (#68)** требует Java-демона и при его отсутствии даёт `PENDING` — поэтому
отдельным Bash-шагом не закладывается, его статус (`PENDING`/результат) фиксируется
в отчёте шагом 7.
- **Отчёт (шаг 7)** — единственная запись прогона: новый файл в `docs/security/`.
Код, схема, миграции и git не трогаются; verify/criterion-гейты коммита не
затрагиваются, так как коммита нет.
Известные долги F-T2 (`/api/admin/*` без app-гейта) и F-P1 (152-ФЗ:
`deals.phones[]` не вычищаются) проверяются фактом в рамках шагов ПДн и угроз и
явно отражаются в вердикте.
### Круг 2
Принято возражение наставника по шагу 4: синхронный `php artisan serve` заблокировал
бы поток и не дал бы выполнить шаги Nuclei. Исправлено — шаг 4 теперь запускает
сервер **фоном** (`php app/artisan serve --host 127.0.0.1 --port 8000 &`), сервер
работает параллельно с последующими шагами сканирования. Остальные шаги наставник
в круге 2 одобрил поимённо (пп. 1–3, 5–7 — OK); изменён только шаг 4.
@@ -0,0 +1,76 @@
# План v3 — Go-Live Security Gate (пред-прод прогон безопасности)
## Цель
Прогнать пять шагов гейта безопасности по спеке (статика → ПДн/152-ФЗ → угрозы →
динамика → вердикт) на текущей ветке `main`, цель — локальная копия
`127.0.0.1:8000`, и собрать письменный отчёт с вердиктом GO / NO-GO. Прогон не
правит продуктовый код, схему и не коммитит; единственная запись — файл отчёта.
```skills-json
["security-go-live", "pdn-152fz-audit", "threat-model"]
```
```steps-json
[
{"op":"Bash","object":"./bin/gitleaks.exe detect --source . --log-opts \"--all\"","ref":"D1"},
{"op":"Bash","object":"npm run sast","ref":"D1"},
{"op":"Bash","object":"./bin/ward.exe scan --path app/","ref":"D1"},
{"op":"Bash","object":"php app/artisan serve --host 127.0.0.1 --port 8000 &","ref":"D4"},
{"op":"Bash","object":"bin/nuclei.exe -u http://127.0.0.1:8000 -tags tech -rate-limit 20 -c 5 -timeout 5 -duc","ref":"D4"},
{"op":"Bash","object":"bin/nuclei.exe -u http://127.0.0.1:8000 -rate-limit 20 -c 5 -timeout 5 -duc -severity medium,high,critical","ref":"D4"},
{"op":"Write","object":"docs/security/2026-06-17-go-live-security-report.md","ref":"D5"}
]
```
```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"}
]
```
## Переговоры
### Круг 1
Доводы по составу шагов:
- **Статика (gitleaks/Semgrep/Ward)** — read-only по локальному репозиторию и
`app/`, ничего не правит и не отправляет наружу. gitleaks читает git-историю,
Semgrep (`npm run sast`) и Ward (`./bin/ward.exe scan`) читают код и конфиг.
Это безопасные локальные проверки.
- **ПДн/152-ФЗ и угрозы** вынесены в навыки `pdn-152fz-audit` и `threat-model`
(объявлены в skills-json) — они анализируют схему и код только чтением; их
находки попадают в отчёт.
- **Динамика (Nuclei)** направлена строго на локальную копию `http://127.0.0.1:8000`
(гард IS8 — боевой контур не трогается). Темп ограничен `-rate-limit 20 -c 5`
под однопоточный `php artisan serve`, чтобы не перегрузить dev-сервер. Цель —
`127.0.0.1`, не `localhost` (резолвер Nuclei на этой машине пропускает `localhost`).
Сначала быстрый smoke по тегу `tech`, затем полный прогон severity medium+.
- **`php artisan serve`** нужен, чтобы портал был доступен сканеру; запускается
фоном на локальном адресе, продуктовых данных и схемы не меняет.
- **ZAP (#68)** требует Java-демона и при его отсутствии даёт `PENDING` — поэтому
отдельным Bash-шагом не закладывается, его статус (`PENDING`/результат) фиксируется
в отчёте шагом 7.
- **Отчёт (шаг 7)** — единственная запись прогона: новый файл в `docs/security/`.
Код, схема, миграции и git не трогаются; verify/criterion-гейты коммита не
затрагиваются, так как коммита нет.
Известные долги F-T2 (`/api/admin/*` без app-гейта) и F-P1 (152-ФЗ:
`deals.phones[]` не вычищаются) проверяются фактом в рамках шагов ПДн и угроз и
явно отражаются в вердикте.
### Круг 2
Принято возражение наставника по шагу 4: синхронный `php artisan serve` заблокировал
бы поток и не дал бы выполнить шаги Nuclei. Исправлено — шаг 4 теперь запускает
сервер **фоном** (`php app/artisan serve --host 127.0.0.1 --port 8000 &`), сервер
работает параллельно с последующими шагами сканирования. Остальные шаги наставник
в круге 2 одобрил поимённо (пп. 1–3, 5–7 — OK); изменён только шаг 4.
### Круг 3
Навыки `security-go-live`, `pdn-152fz-audit` и `threat-model` уже вызваны; их
находки (ПДн Режим 1/2, STRIDE-угрозы, статус известных долгов F-T2/F-P1) войдут в
итоговый отчёт шагом 7. Состав и порядок шагов неизменны относительно круга 2.
@@ -0,0 +1,81 @@
# План v4 — Go-Live Security Gate (пред-прод прогон безопасности)
## Цель
Собрать единый письменный отчёт безопасности портала Лидерра с вердиктом
**GO / NO-GO** на текущей ветке `main`, цель — локальная копия `127.0.0.1:8000`.
Гейт концептуально состоит из пяти проверок, каждая со своим механизмом исполнения:
- **статика** — Bash-шаги gitleaks, Semgrep (`npm run sast`), Ward;
- **ПДн / 152-ФЗ** — объявленный навык `pdn-152fz-audit` (Режим 1 и 2);
- **угрозы STRIDE** — объявленный навык `threat-model`;
- **динамика** — Bash-шаги: запуск локального сервера и Nuclei по `127.0.0.1`;
- **сводный вердикт** — шаг записи отчёта (последний), консолидирующий находки
Bash-инструментов и навыков в один файл с GO/NO-GO.
Прогон не правит продуктовый код, схему и не коммитит; единственная запись —
файл отчёта в `docs/security/`.
```skills-json
["security-go-live", "pdn-152fz-audit", "threat-model"]
```
```steps-json
[
{"op":"Bash","object":"./bin/gitleaks.exe detect --source . --log-opts \"--all\"","ref":"D1"},
{"op":"Bash","object":"npm run sast","ref":"D1"},
{"op":"Bash","object":"./bin/ward.exe scan --path app/","ref":"D1"},
{"op":"Bash","object":"php app/artisan serve --host 127.0.0.1 --port 8000 &","ref":"D4"},
{"op":"Bash","object":"bin/nuclei.exe -u http://127.0.0.1:8000 -tags tech -rate-limit 20 -c 5 -timeout 5 -duc","ref":"D4"},
{"op":"Bash","object":"bin/nuclei.exe -u http://127.0.0.1:8000 -rate-limit 20 -c 5 -timeout 5 -duc -severity medium,high,critical","ref":"D4"},
{"op":"Write","object":"docs/security/2026-06-17-go-live-security-report.md","ref":"D5"}
]
```
```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"}
]
```
## Переговоры
### Круг 1
Доводы по составу шагов:
- **Статика (gitleaks/Semgrep/Ward)** — read-only по локальному репозиторию и
`app/`, ничего не правит и не отправляет наружу. gitleaks читает git-историю,
Semgrep (`npm run sast`) и Ward (`./bin/ward.exe scan`) читают код и конфиг.
- **ПДн/152-ФЗ и угрозы** выполняются навыками `pdn-152fz-audit` и `threat-model`
(объявлены в skills-json) — они анализируют схему и код только чтением; их
находки попадают в отчёт шагом 7. Эти две проверки — не Bash-шаги, а навыки.
- **Динамика (Nuclei)** направлена строго на локальную копию `http://127.0.0.1:8000`
(гард IS8). Темп ограничен `-rate-limit 20 -c 5` под однопоточный
`php artisan serve`. Цель — `127.0.0.1`, не `localhost` (резолвер Nuclei
пропускает `localhost`). Сначала smoke по тегу `tech`, затем полный medium+.
- **`php artisan serve`** запускается фоном на локальном адресе, продуктовых данных
и схемы не меняет.
- **ZAP (#68)** требует Java-демона; при его отсутствии — `PENDING`, статус
фиксируется в отчёте шагом 7 (отдельным Bash-шагом не закладывается).
- **Отчёт (шаг 7)** — единственная запись прогона: новый файл в `docs/security/`.
Код, схема, миграции и git не трогаются; коммита нет.
Известные долги F-T2 (`/api/admin/*` без app-гейта) и F-P1 (152-ФЗ:
`deals.phones[]` не вычищаются) проверяются фактом и явно отражаются в вердикте.
### Круг 2
Возражение по синхронному `php artisan serve` принято: шаг 4 запускает сервер
**фоном** (`… &`), он работает параллельно с шагами Nuclei. Остальные шаги
одобрены поимённо (пп. 1–3, 5–7 — OK).
### Круг 3
Пять концептуальных проверок гейта сопоставлены с механизмом исполнения прямо в
`## Цель`: статика и динамика — Bash-шаги steps-json; ПДн и угрозы — объявленные
навыки skills-json (не Bash-шаги); сводный вердикт — шаг 7 записи отчёта. Семь
строк steps-json покрывают проверки «статика» (3), «динамика» (3) и «вердикт» (1);
проверки «ПДн» и «угрозы» исполняются навыками. Несостыковки между числом
концептуальных проверок и числом Bash-шагов нет — это разные механизмы.
@@ -0,0 +1,62 @@
# План — Go-Live Security Gate (пред-прод прогон безопасности)
## Цель
Прогнать пять шагов гейта безопасности по спеке (статика → ПДн/152-ФЗ → угрозы →
динамика → вердикт) на текущей ветке `main`, цель — локальная копия
`127.0.0.1:8000`, и собрать письменный отчёт с вердиктом GO / NO-GO. Прогон не
правит продуктовый код, схему и не коммитит; единственная запись — файл отчёта.
```skills-json
["security-go-live", "pdn-152fz-audit", "threat-model"]
```
```steps-json
[
{"op":"Bash","object":"./bin/gitleaks.exe detect --source . --log-opts \"--all\"","ref":"D1"},
{"op":"Bash","object":"npm run sast","ref":"D1"},
{"op":"Bash","object":"./bin/ward.exe scan --path app/","ref":"D1"},
{"op":"Bash","object":"php app/artisan serve --host 127.0.0.1 --port 8000","ref":"D4"},
{"op":"Bash","object":"bin/nuclei.exe -u http://127.0.0.1:8000 -tags tech -rate-limit 20 -c 5 -timeout 5 -duc","ref":"D4"},
{"op":"Bash","object":"bin/nuclei.exe -u http://127.0.0.1:8000 -rate-limit 20 -c 5 -timeout 5 -duc -severity medium,high,critical","ref":"D4"},
{"op":"Write","object":"docs/security/2026-06-17-go-live-security-report.md","ref":"D5"}
]
```
```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"}
]
```
## Переговоры
### Круг 1
Доводы по составу шагов:
- **Статика (gitleaks/Semgrep/Ward)** — read-only по локальному репозиторию и
`app/`, ничего не правит и не отправляет наружу. gitleaks читает git-историю,
Semgrep (`npm run sast`) и Ward (`./bin/ward.exe scan`) читают код и конфиг.
Это безопасные локальные проверки.
- **ПДн/152-ФЗ и угрозы** вынесены в навыки `pdn-152fz-audit` и `threat-model`
(объявлены в skills-json) — они анализируют схему и код только чтением; их
находки попадают в отчёт.
- **Динамика (Nuclei)** направлена строго на локальную копию `http://127.0.0.1:8000`
(гард IS8 — боевой контур не трогается). Темп ограничен `-rate-limit 20 -c 5`
под однопоточный `php artisan serve`, чтобы не перегрузить dev-сервер. Цель —
`127.0.0.1`, не `localhost` (резолвер Nuclei на этой машине пропускает `localhost`).
Сначала быстрый smoke по тегу `tech`, затем полный прогон severity medium+.
- **`php artisan serve`** нужен, чтобы портал был доступен сканеру; запускается
фоном на локальном адресе, продуктовых данных и схемы не меняет.
- **ZAP (#68)** требует Java-демона и при его отсутствии даёт `PENDING` — поэтому
отдельным Bash-шагом не закладывается, его статус (`PENDING`/результат) фиксируется
в отчёте шагом 7.
- **Отчёт (шаг 7)** — единственная запись прогона: новый файл в `docs/security/`.
Код, схема, миграции и git не трогаются; verify/criterion-гейты коммита не
затрагиваются, так как коммита нет.
Известные долги F-T2 (`/api/admin/*` без app-гейта) и F-P1 (152-ФЗ:
`deals.phones[]` не вычищаются) проверяются фактом в рамках шагов ПДн и угроз и
явно отражаются в вердикте.
@@ -0,0 +1,36 @@
# План — дополнение операционного гайда уроками прогона
## Цель
Внести в существующий операционный гайд `docs/superpowers/router-mentor-wall-GUIDE.md`
один новый раздел с двумя уроками прогона go-live security gate (контекст до
печати плана; дефекты проверяющего и указателя) — аддитивной правкой, не трогая
остальной текст и оглавление. Код, схема и git не затрагиваются.
```skills-json
[]
```
```steps-json
[
{"op":"Edit","object":"docs/superpowers/router-mentor-wall-GUIDE.md","ref":"G3"}
]
```
```verified-context-json
[
{"id":"vc1","kind":"EXTRACTED","ref":"docs/superpowers/router-mentor-wall-GUIDE.md","anchor":"Читать ПЕРЕД работой под стеной"},
{"id":"vc2","kind":"EXTRACTED","ref":"docs/superpowers/specs/2026-06-17-wall-guide-lessons-spec-v2.md","anchor":"весь контекст до печати плана"}
]
```
## Переговоры
### Круг 1
Правка единственная и аддитивная: один шаг Edit добавляет новый раздел второго
уровня в операционный гайд рядом с разделом «Частые ошибки». Продуктовый код,
схема БД, миграции и git не затрагиваются; навыки не требуются (skills-json пуст).
Содержание раздела — два урока из спеки v2 (контекст до печати плана + наблюдаемые
дефекты проверяющего/указателя). Гайд — обычный документ, не машинерия стены
(не `enforce-*`/`judge-*`/`floor-*`), поэтому правится шагом плана штатно.
@@ -71,6 +71,33 @@
- **Наставник/судья async** (~25-32с). Печать встаёт не мгновенно. Per-attempt таймаут 90с
(`HEAVY_LLM_TIMEOUT_MS`). degraded («не дозвонился») → просто повтор.
## Уроки живого прогона (go-live аудит 17.06.2026)
**Урок 1 — весь контекст ДО печати плана.** В режиме реализации (под опечатанным
планом) чтение вне пути текущего шага блокируется гейтом ДР-1 — нельзя прочитать
ни исходники для анализа, ни даже собственный вывод инструмента (temp-файл
сканера). Поэтому всю разведку (схема, роуты, конфиги, reference-файлы навыков,
вывод сканеров) собирай ДО печати плана — в разговорном режиме чтение свободно.
**Запасной канал**, если контекст понадобился уже под планом: владелец вставляет
содержимое файла прямо в чат — это контекст разговора, а не вызов Read, и
дисциплина чтения его не трогает. Вывод: для аудита/разведки сперва читаешь всё
нужное, потом печатаешь спеку→план, где шаги уже не требуют новых чтений.
**Урок 2 — дефекты проверяющего и указателя (знать заранее).**
- *Недетерминизм судьи.* Судья может выдать разный вердикт на байт-идентичном
тексте между кругами (наблюдалось: круг N — NO-GO `[fatal]` на строке «Цель»,
круг N+1 — GO на том же тексте). Лечение — просто повторить печать (async-повтор).
- *Рассинхрон указателя (F-J на практике).* Ранний хук цепочки (`supreme-gate`)
сдвигает указатель шага, а более поздний PreToolUse-блокер (skill-discipline)
роняет само действие → шаг считается пройденным, хотя инструмент не выполнился
(на прогоне так был пропущен первый шаг gitleaks). Перепечатка плана
байт-в-байт НЕ сбрасывает указатель (тот же `plan_id`) — нужен план с другим
именем/содержимым, либо `FLOOR-ESCAPE: plan-done`.
- *Профилактика для скил-планов.* Объявленные в `skills-json` навыки вызови
(Skill) ПЕРВЫМ делом после печати, ДО первого мутирующего/Bash-шага — иначе
skill-discipline уронит первый шаг и уведёт указатель.
## ⛔ Нельзя: правка машинерии стены (F-K)
Файлы `tools/enforce-*.mjs`, `judge-*`, `mentor-*`, `floor-*`, `escape-grant`, `plan-lock` и т.п. —
@@ -0,0 +1,128 @@
# Спека — 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"}
]
```
@@ -0,0 +1,63 @@
# Спека v2 — требования к дополнению операционного гайда уроками прогона
## Цель
Зафиксировать требования к дополнению операционного гайда
`docs/superpowers/router-mentor-wall-GUIDE.md`: какие два урока прогона go-live
security gate (17.06.2026) должны быть в нём отражены, с каким содержанием и в
каком месте файла. Документ описывает контракт, edge-cases и критерии приёмки
правки; сама правка выполняется отдельным шагом плана, не этим документом.
## Урок 1 — весь контекст до печати плана {#G1}
**Контракт.** В режиме реализации (под опечатанным планом) чтение вне пути
текущего шага блокируется — нельзя прочитать ни исходники для анализа, ни даже
собственный вывод инструмента (temp-файл сканера). Поэтому всю разведку (схема,
роуты, конфиги, reference-файлы навыков, вывод сканеров) собирают ДО печати плана,
в разговорном режиме, где чтение свободно.
**Edge-case / запасной канал.** Если контекст всё же понадобился в режиме
реализации — владелец вставляет содержимое файла прямо в чат: это контекст
разговора, а не вызов Read-инструмента, поэтому дисциплина чтения его не трогает.
**Конвенция.** Для аудита/разведки: сперва читаешь всё нужное, формулируешь
находки, и только потом печатаешь спеку→план, где шаги уже не требуют новых чтений.
**Критерий приёмки правки.** В гайде есть пункт, явно предписывающий собрать
чтения до печати плана и называющий приём «вставка в чат» как запасной канал.
## Урок 2 — наблюдаемые дефекты проверяющего и указателя {#G2}
**Контракт.** На прогоне зафиксированы два дефекта машинерии, о которых сессия
должна знать заранее: (1) проверяющий план может выдать разный вердикт на
байт-идентичном тексте между кругами (наблюдалось NO-GO с пометкой fatal на одной
строке цели, затем GO на том же тексте); (2) рассинхрон указателя — ранний хук в
цепочке сдвигает указатель шага, а более поздний хук роняет само действие, из-за
чего шаг считается пройденным, хотя инструмент не выполнился (на прогоне так был
пропущен первый шаг).
**Edge-case.** Перепечатка плана байт-в-байт не сбрасывает указатель (тот же
идентификатор плана) — для сброса нужен план с другим именем/содержимым.
**Конвенция.** При недетерминированном вердикте — повторить печать (async-повтор);
при рассинхроне указателя — новый план с другим именем либо завершить застрявший
план резерв-меткой.
**Критерий приёмки правки.** В гайде есть пункт, называющий оба дефекта и
отсылающий к уже описанным средствам восстановления (новый план / резерв-метка).
## Куда вписать {#G3}
**Контракт.** Новый раздел добавляется в существующий гайд как отдельная секция
(заголовок второго уровня) рядом с разделом «Частые ошибки», не ломая остальную
структуру и оглавление.
**Критерий приёмки правки.** Раздел присутствует в файле гайда; прежний текст не
повреждён.
```verified-context-json
[
{"id":"vc1","kind":"EXTRACTED","ref":"docs/superpowers/router-mentor-wall-GUIDE.md","anchor":"Читать ПЕРЕД работой под стеной"},
{"id":"vc2","kind":"EXTRACTED","ref":"docs/security/2026-06-17-go-live-security-report.md","anchor":"SECURITY GO-LIVE REPORT"}
]
```
@@ -0,0 +1,62 @@
# Спека — уроки прогона аудита в операционный гайд
## Цель
Добавить в операционный гайд `docs/superpowers/router-mentor-wall-GUIDE.md`
короткий раздел с уроками, выявленными на живом многошаговом прогоне (go-live
security gate 17.06.2026): порядок сбора контекста перед многошаговой работой,
особенности чтения вывода инструментов и наблюдаемые дефекты проверяющего/
указателя. Цель — чтобы следующая сессия не повторяла те же грабли.
## Урок 1 — весь контекст до печати плана {#G1}
**Контракт.** В режиме реализации (под опечатанным планом) чтение вне пути
текущего шага блокируется — нельзя прочитать ни исходники для анализа, ни даже
собственный вывод инструмента (temp-файл сканера). Поэтому всю разведку (схема,
роуты, конфиги, reference-файлы навыков, вывод сканеров) собирают ДО печати плана,
в разговорном режиме, где чтение свободно.
**Edge-case / обход.** Если контекст всё же понадобился в режиме реализации —
владелец вставляет содержимое файла прямо в чат: это контекст разговора, а не
вызов Read-инструмента, поэтому дисциплина чтения его не трогает.
**Конвенция.** Для аудита/разведки: сперва читаешь всё нужное, формулируешь
находки, и только потом печатаешь спеку→план, где шаги уже не требуют новых чтений.
**Критерий.** В гайде есть пункт, явно предписывающий собрать чтения до печати
плана и называющий приём «вставка в чат» как запасной канал.
## Урок 2 — наблюдаемые дефекты проверяющего и указателя {#G2}
**Контракт.** На прогоне зафиксированы два дефекта машинерии, о которых сессия
должна знать заранее: (1) проверяющий план может выдать разный вердикт на
байт-идентичном тексте между кругами (наблюдалось NO-GO с пометкой fatal на одной
строке цели, затем GO на том же тексте); (2) рассинхрон указателя — ранний хук в
цепочке сдвигает указатель шага, а более поздний хук роняет само действие, из-за
чего шаг считается пройденным, хотя инструмент не выполнился (на прогоне так был
пропущен первый шаг).
**Edge-case.** Перепечатка плана байт-в-байт не сбрасывает указатель (тот же
идентификатор плана) — для сброса нужен план с другим именем/содержимым.
**Конвенция.** При недетерминированном вердикте — повторить печать (это уже
описано как async-повтор); при рассинхроне указателя — новый план с другим именем
либо завершить застрявший план резерв-меткой.
**Критерий.** В гайде есть пункт, называющий оба дефекта и отсылающий к уже
описанным средствам восстановления (новый план / резерв-метка завершения).
## Куда вписать {#G3}
**Контракт.** Новый раздел добавляется в существующий гайд как отдельная секция
(заголовок второго уровня) рядом с разделом «Частые ошибки», не ломая остальную
структуру и оглавление.
**Критерий.** Раздел присутствует в файле гайда; прежний текст не повреждён.
```verified-context-json
[
{"id":"vc1","kind":"EXTRACTED","ref":"docs/superpowers/router-mentor-wall-GUIDE.md","anchor":"Читать ПЕРЕД работой под стеной"},
{"id":"vc2","kind":"EXTRACTED","ref":"docs/security/2026-06-17-go-live-security-report.md","anchor":"SECURITY GO-LIVE REPORT"}
]
```