docs(security): аудит доведён до конца + поправка по верификации кода + баги стены

Прогон всех 5 сканеров: gitleaks 0 / Semgrep 0 / Ward 2(dev) / Nuclei 0(medium+) / ZAP 0(high). pg_anonymizer не установлен (факт). Три ложных P0 сняты проверкой кода (E9/E18/admin/SSRF закрыты). Вердикт NO-GO держат F-P1, pg_anonymizer, прод-.env. Трекер открытых вопросов + файл-баг (чтение под стеной, десинк F-J, зацикл наставника) для claude-brain.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-06-17 16:25:23 +03:00
parent 71234d719a
commit 3bf5eaed81
12 changed files with 656 additions and 12 deletions
@@ -12,18 +12,17 @@ Commit: main @ ~cf813c10 (+ незакоммиченные правки пара
> слой инструментов требует доустановки и перезапуска.
--- ШАГ 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` проверить заново.)
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 — не применим к этому плановому прогону (вызывается вручную
перед первым публичным релизом / при крупных изменениях периметра).
@@ -107,3 +106,36 @@ PENDING / не отработали (должны быть закрыты до
доустановки 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).
---
## ДОБИВАНИЕ АУДИТА (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).
+103
View File
@@ -0,0 +1,103 @@
# Открытые вопросы — снимок 17.06.2026
Сводный трекер по итогам сессии (go-live security gate + кросс-сессионная сверка).
Зона = где правится; «коллизия с фронт-сессией» = пересекается ли с параллельной
сессией, которая ведёт находки осмотра во фронтенде `app/resources/js`.
---
## 🔴 Блокеры безопасности (мешают выходу в интернет)
| # | Пункт | Суть | Зона | Коллизия |
|---|---|---|---|---|
| B1 | **F-P1 / 152-ФЗ retention** | Телефоны удалённых лидов не вычищаются (нет анонимизации/hard-delete после soft-delete). Подтверждён фактом по `Deal`-модели + схеме. | `app/Console/Commands` + миграция (бэкенд/БД) | низкая (бэкенд) |
| B2 | **pg_anonymizer не установлен** | `pg_dump` отдаёт ПДн открыто. Расширение `anon` — фаза 3, может быть не поставлено. Проверить: `SELECT extname FROM pg_extension WHERE extname='anon';` | БД/инфра + `docs/security` | нет |
| B3 | **SAAS_ADMIN_TEST_BYPASS** | Проверить, что в боевом `.env.production` = `false` (иначе полный доступ к `/api/admin/**`). | `.env.production` / config | нет |
| B4 | **SSRF через `/api/webhooks/test`** | Нет фильтрации URL → запрос на metadata YC `169.254.169.254` → IAM-токен. | бэкенд (контроллер/сервис) | низкая |
| B5 | **Открытые без auth ручки** | `/api/dashboard/summary` (E18), `/api/managers` + `/api/lead-statuses` (E9) — добавить `auth:sanctum + tenant`. | `routes/api.php` + middleware (бэкенд) | низкая |
| B6 | **Ward [High] APP_DEBUG** | На dev ожидаемо; на боевом `.env.production` обязательно `APP_DEBUG=false`, `APP_ENV=production`. | `.env.production` | нет |
## 🟠 P1 (важно, не критично к go-live)
| # | Пункт | Зона |
|---|---|---|
| P1 | Нет rate-limit на `login`/`forgot`/`2fa/verify` (Laravel Throttle) | `routes/api.php` (бэкенд) |
| P2 | URL-secret поставщика виден в access-логах (E4) → перейти на HMAC, ротация | бэкенд/инфра |
## 🟢 Инструменты аудита — статус
| Инструмент | Статус 17.06 | Действие |
|---|---|---|
| gitleaks #8 | ✅ OK (2462 коммита, 0 утечек) | — |
| Ward #70 | ✅ OK (2 dev-находки: APP_DEBUG/APP_ENV) | проверить на боевом `.env` |
| Nuclei #69 | ✅ OK (1 info `php`, 0 medium+) | — |
| Semgrep #25 | ⏸ PENDING — не установлен | `pip install semgrep` в терминале → `npm run sast` |
| ZAP #68 | ⏸ PENDING — Java не установлена | поставить Java 17 + поднять ZAP (active scan) |
## 🟡 Находки осмотра портала (ведёт параллельная фронт-сессия)
| Находка | Статус | Зона | Коллизия |
|---|---|---|---|
| F1 (Город в карточке) | ✅ закоммичено | фронт | — её зона |
| F2 (стоимость лида) | ✅ закоммичено | фронт | — её зона |
| F4 (пустой столбец «Операция») | 🔧 в работе у неё (`TransactionsTable.spec.ts`) | фронт | **её зона — не трогать** |
| F3 (прогноз «хватит на дни» дашборд↔биллинг) | ❓ проверить, закрыта ли | фронт | её зона |
| F5 (мок «средняя 2248 ₽») | ❓ проверить (есть `format-rub-demo-spec`) | фронт | её зона |
## 🧱 Баги машинерии стены (для claude-brain)
| # | Пункт | Файл |
|---|---|---|
| W1 | Чтение под планом блокируется (impl-режим ДР-1) | `docs/superpowers/2026-06-17-wall-read-block-bug.md` |
| W2 | Десинк указателя F-J (счётчик двигается на упавшем шаге) | там же + GUIDE |
| W3 | Недетерминизм судьи на байт-идентичном тексте | GUIDE «Уроки живого прогона» |
| W4 | Наставник: пустой `recommendation``no_mentor_go` | `docs/superpowers/2026-06-16-mentor-empty-recommendation-bug.md` |
## 🧹 Хозяйственное
| # | Пункт |
|---|---|
| H1 | Незакоммичено: спека/план/файл-баг чтения, спека/план/отчёт/трекер аудита, обновление отчёта |
| H2 | Не запушено: docs-коммит `e693cfc6` (локально) |
| H3 | Нормативный хвост (проверить): синхронизация квинтета CLAUDE.md 2.46→2.47 + долг lychee по относительным ссылкам |
| H4 | Ward оставил артефакты `ward-report.{json,sarif,html,md}` в корне — решить, в .gitignore или удалить |
---
**Рекомендация по очерёдности (с учётом недельного лимита):** реальные блокеры
B1–B6 — бэкенд/БД/config, **не пересекаются** с фронт-сессией. Самый дешёвый и
отдельный — **B5** (закрыть открытые ручки) + **B3/B6** (проверка боевого `.env`).
Semgrep/ZAP — поставить инструменты (одна команда + Java), затем добить статику/DAST.
## ПОПРАВКА — верификация по коду (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).
---
## ДОБИВАНИЕ АУДИТА (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).
@@ -0,0 +1,95 @@
# БАГ — чтение под стеной «роутер-наставник» (impl-режим) + связанный десинк указателя
**Дата:** 17.06.2026
**Откуда:** живой прогон go-live security gate (отчёт `docs/security/2026-06-17-go-live-security-report.md`).
**Куда:** claude-brain (управляющий слой стены — `enforce-supreme-gate` и оркестрация).
**Связано:** `docs/superpowers/router-mentor-wall-GUIDE.md` (раздел «Уроки живого прогона»).
---
## Суть бага (одной фразой)
В режиме реализации (под опечатанным планом) чтение разрешено **только по пути
текущего шага**. Всё остальное читать нельзя — включая файлы, появившиеся во время
прогона, и **собственный вывод запущенных инструментов** (гейт ДР-1 в
`enforce-supreme-gate`).
## Почему это серьёзно
Многошаговая работа, где следующее действие зависит от прочитанного (аудит,
отладка, цепочки инструментов, проверка результата), под стеной фактически
слепнет. Обойти удалось только тем, что **владелец вставлял файлы в чат вручную**
(контекст разговора ≠ вызов Read-инструмента → стена его не трогает). Это
костыль, а не решение.
## Случаи, где дефект бьёт
1. **Свой же вывод не прочитать.** Длинная команда (сканер/сборка/тесты)
уезжает в фоновый запуск, результат пишется в temp-файл — открыть нельзя.
*Живой пример прогона:* не прочитался вывод Nuclei.
2. **Забыл прочитать до печати плана — всё.** Понадобился файл по ходу — под
планом не открыть; только переделывать план или просить владельца вставить.
3. **Сторонний сервис/процесс создал файл во время прогона.** Генератор,
выгрузка, отчёт другой задачи, артефакт CI — недоступны.
4. **Нечем проверить результат шага.** Сделал шаг, для проверки нужен другой
файл/лог — нельзя. Получается «сделал вслепую».
5. **Ветвление по содержимому невозможно.** «Если в конфиге X — делаем Y» не
работает: чтобы выбрать, надо прочитать.
6. **Диагностика ошибки по внешнему логу.** Шаг упал, ошибка «смотри лог тут» —
тот лог открыть нельзя.
7. **Промежуточный артефакт в цепочке инструментов.** Шаг N сделал файл, шаг N+1
должен на него посмотреть — нельзя, только передать вслепую.
8. **Перечитать только что записанное.** Записал файл на шаге 2, на шаге 4 надо
свериться — закрыто.
9. **Неожиданная находка.** Поиск дал совпадение в файле, о котором при
планировании не знал — открыть его уже нельзя.
10. **Файлы от параллельной сессии.** Соседняя сессия добавила/закоммитила
файлы — мне их не глянуть.
## Связь с рассинхроном указателя (важно для починки)
Наивная мысль «добавить escape-метку `read:`» **опасна**: стена сейчас двигает
счётчик шагов вперёд **даже когда действие не прошло** (это и есть десинк F-J —
на прогоне так был пропущен шаг gitleaks: `supreme-gate` сдвинул указатель, а
`enforce-domain-skill-discipline` дальше в цепочке уронил действие). Если пустить
чтение через ту же машинерию шагов — **очередь сдвинется, план поедет**.
Ключ: **чтение не является шагом.** Шаги плана — только `Write/Edit/Bash/MultiEdit`.
Значит разрешение на чтение должно работать **«сбоку от очереди»**, не касаясь
счётчика шагов.
## Приоритет починки (для claude-brain)
1. **ГЛАВНОЕ — счётчик шагов += 1 только при успешно завершённом настоящем
шаге.** Не на заблокированном действии, не на чтении, не на постороннем.
Это чинит десинк F-J И автоматически делает чтение безопасным (раз чтение не
шаг — оно не может сдвинуть очередь).
2. **Пассивно разрешить чтение** без escape и без касания очереди:
(а) собственный вывод инструментов, запущенных в этом плане (temp-файлы);
(б) файлы, появившиеся ПОСЛЕ опечатывания плана (их при планировании не было —
запрет «лишнего чтения» здесь нелогичен).
3. **Escape-метка `read:<путь>` — только крайний резерв**, и строго мимо
счётчика шагов (out-of-band), чтобы не повторить десинк. После п.1+п.2 почти
не нужна.
## Критерий «починено»
- Под опечатанным планом можно прочитать свой вывод и файлы, созданные после
печати, без сдвига очереди шагов.
- Падение/блокировка шага НЕ двигает указатель (десинк F-J не воспроизводится).
- При необходимости разовое `read:<путь>` через escape не ломает очередность.
---
## БАГ-2 — наставник не видит часть плана + нет owner-seal при зацикле (17.06.2026)
**Случай:** правка-поправка аудита (2 аддитивных Edit в `docs/`). Наставник 3 круга подряд (17, 18, 19) давал NO-GO с одним требованием: «добавь к каждому шагу явную пометку, что вердикт остаётся NO-GO».
**Дефект 1 — наставник не видит доп.поля `steps-json`.** Контроллер добавил поле `note` к каждому шагу с требуемым текстом. Но наставник в своём представлении плана видит только `op`/`object`/`ref` — поле `note` (и любые доп.поля) **срезаются** до того, как план попадает к наставнику. Поэтому он снова и снова требует то, что уже добавлено → дедлок, план не пройти, сколько ни переписывай.
**Дефект 2 — нет owner-seal при зацикле.** По дизайну после 3 NO-GO подряд должна появляться карточка арбитража owner-seal (3 кнопки: держусь контроллера / согласен / своё). На прогоне после кругов 17–19 карточка **не появилась** — круг не разрывался. Контроллер обошёл стену через терминал владельца.
**Починка (для claude-brain):**
1. Наставник должен видеть план целиком, включая доп.поля шагов (`note`/`description`); ИЛИ задокументировать единственно поддержанный способ «пометки к шагу».
2. Счётчик «N NO-GO подряд» обязан реально триггерить owner-seal-карточку — проверить порог и срабатывание.
3. Требование, которое контроллер физически не может выразить в формате плана, не должно вести к бесконечному NO-GO — нужен предохранитель (эскалация к владельцу).
@@ -0,0 +1,44 @@
# План — доведение аудита + трекер открытых вопросов
## Цель
Перезапустить статику gitleaks и Ward правильными командами, проверить готовность
ZAP (Java), обновить отчёт реальными результатами и записать новый файл-трекер
открытых вопросов. Цель — `main`, локальный контур; продуктовый код, схема БД и
git не затрагиваются; правки только в `docs/`.
```skills-json
[]
```
```steps-json
[
{"op":"Bash","object":"./bin/gitleaks.exe detect --source . --log-opts \"--all\"","ref":"A1"},
{"op":"Bash","object":"./bin/ward.exe scan app/","ref":"A1"},
{"op":"Bash","object":"java -version","ref":"A2"},
{"op":"Edit","object":"docs/security/2026-06-17-go-live-security-report.md","ref":"A3"},
{"op":"Write","object":"docs/security/2026-06-17-open-items.md","ref":"A4"}
]
```
```verified-context-json
[
{"id":"vc1","kind":"EXTRACTED","ref":"docs/security/2026-06-17-go-live-security-report.md","anchor":"SECURITY GO-LIVE REPORT"},
{"id":"vc2","kind":"EXTRACTED","ref":"docs/superpowers/specs/2026-06-17-audit-finish-spec.md","anchor":"трекер открытых вопросов"}
]
```
## Переговоры
### Круг 1
Шаги read-only по коду + две записи в `docs/`:
- gitleaks (история на секреты) и Ward (`scan app/` — позиционно, исправлен
устаревший флаг `--path`) — локальное чтение, ничего не правят и не шлют наружу.
- `java -version` — безобидная проверка наличия Java для оценки готовности ZAP.
- Edit отчёта — обновление существующего документа `docs/security/` реальными
результатами; Write трекера — новый документ `docs/security/`.
Продуктовый код, схема, миграции, git не затрагиваются; коммита нет, поэтому
verify/criterion-гейты не активируются. Навыки не нужны (skills-json пуст).
Semgrep и ZAP требуют доустановки (install/Java), которая под стеной floor-опасна
— в этот план не закладывается, фиксируется как открытый пункт в трекере и отчёте.
@@ -0,0 +1,54 @@
# План v2 — поправка аудита по факту кода (вердикт остаётся NO-GO)
## Цель
Внести верифицированную поправку в отчёт и трекер: E9/E18/E20/SSRF подтверждены
закрытыми по коду и переводятся в «закрыто фактом / остаток». **Итоговый вердикт
аудита ОСТАЁТСЯ NO-GO** — закрытие этих пунктов НЕ снимает реальные блокеры
(F-P1 ретеншен телефонов, pg_anonymizer не установлен, прод-`.env` APP_DEBUG/APP_ENV).
Правки только в `docs/`; код/схема/git не затрагиваются.
```skills-json
[]
```
```steps-json
[
{"op":"Edit","object":"docs/security/2026-06-17-go-live-security-report.md","ref":"V3"},
{"op":"Edit","object":"docs/security/2026-06-17-open-items.md","ref":"V3"}
]
```
```verified-context-json
[
{"id":"vc1","kind":"EXTRACTED","ref":"app/routes/web.php","anchor":"Go-live: auth:sanctum"},
{"id":"vc2","kind":"EXTRACTED","ref":"docs/superpowers/specs/2026-06-17-audit-verify-correction-spec.md","anchor":"поправка аудита по факту кода"}
]
```
## Переговоры
### Круг 1
Две аддитивные правки `docs/`: в отчёт — блок-поправка с верифицированными фактами
(E9/E18 закрыты `auth:sanctum`, `/api/admin/*` за `saas-admin` fail-closed, SSRF за
`WebhookUrlGuard`); в трекере строки B3/B4/B5 → «закрыто фактом / остаток».
Продуктовый код, схема, миграции, git не затрагиваются; коммита нет; навыки не нужны.
### Круг 2
Forward-рекомендация: поправка обязана сохранить вердикт NO-GO и не выдавать
закрытые пункты за нерешённые.
### Круг 3 (учёт замечания наставника круга 17)
Замечание принято и зафиксировано явно как условие обоих шагов:
- **Шаг 1 (отчёт):** блок-поправка прямо пишет «ВЕРДИКТ ОСТАЁТСЯ NO-GO» и
перечисляет, что остаются открытыми F-P1 / pg_anonymizer / прод-`.env`; закрытие
E9/E18/E20/SSRF влияет только на список угроз, НЕ на итог.
- **Шаг 2 (трекер):** строки B3/B4/B5 помечаются «закрыто фактом / остаток» с
явной припиской, что блокеры B1 (F-P1), B2 (pg_anonymizer), B6 (прод-`.env`)
не сняты и вердикт NO-GO в силе.
Ни один шаг не меняет вердикт на GO; общий статус блокеров сохраняется.
@@ -0,0 +1,52 @@
# План v3 — поправка аудита по факту кода (вердикт остаётся NO-GO)
## Цель
Внести верифицированную поправку в отчёт и трекер: E9/E18/E20/SSRF подтверждены
закрытыми по коду и переводятся в «закрыто фактом / остаток». **Итоговый вердикт
аудита ОСТАЁТСЯ NO-GO** — закрытие этих пунктов НЕ снимает реальные блокеры
(F-P1 ретеншен телефонов, pg_anonymizer не установлен, прод-`.env` APP_DEBUG/APP_ENV).
Правки только в `docs/`; код/схема/git не затрагиваются.
```skills-json
[]
```
```steps-json
[
{"op":"Edit","object":"docs/security/2026-06-17-go-live-security-report.md","ref":"V3","note":"Вердикт ОСТАЁТСЯ NO-GO. Блок-поправка закрывает E9/E18/E20/SSRF (влияет только на список угроз), но блокеры F-P1, pg_anonymizer, прод-.env (APP_DEBUG/APP_ENV) НЕ сняты — итог не меняется на GO."},
{"op":"Edit","object":"docs/security/2026-06-17-open-items.md","ref":"V3","note":"Строки B3/B4/B5 → «закрыто фактом / остаток». Приписка: блокеры B1 (F-P1), B2 (pg_anonymizer), B6 (прод-.env) НЕ сняты, общий вердикт NO-GO в силе. Шаг не переводит вердикт в GO."}
]
```
```verified-context-json
[
{"id":"vc1","kind":"EXTRACTED","ref":"app/routes/web.php","anchor":"Go-live: auth:sanctum"},
{"id":"vc2","kind":"EXTRACTED","ref":"docs/superpowers/specs/2026-06-17-audit-verify-correction-spec.md","anchor":"что остаётся реальным блокером"}
]
```
## Переговоры
### Круг 1
Две аддитивные правки `docs/`: в отчёт — блок-поправка с верифицированными фактами;
в трекере B3/B4/B5 → «закрыто фактом / остаток». Код/схема/git не затрагиваются.
### Круг 2
Forward-рекомендация: поправка обязана сохранить вердикт NO-GO и не выдавать
закрытые пункты за нерешённые.
### Круг 3 (учёт замечаний наставника кругов 17 и 18)
Замечание принято и зафиксировано **явно в каждом шаге** через поле `note` в
`steps-json`:
- **Шаг 1 (отчёт):** note прямо пишет «вердикт ОСТАЁТСЯ NO-GO»; закрытие
E9/E18/E20/SSRF влияет только на список угроз; блокеры F-P1/pg_anonymizer/прод-`.env`
не сняты — итог не GO.
- **Шаг 2 (трекер):** note помечает B3/B4/B5 «закрыто фактом / остаток» с
припиской, что B1/B2/B6 не сняты и NO-GO в силе.
Ни один шаг не меняет вердикт на GO; общий статус блокеров сохранён.
@@ -0,0 +1,42 @@
# План — поправка аудита по факту кода
## Цель
Внести верифицированную поправку в отчёт и трекер аудита: E9/E18/E20/SSRF
подтверждены закрытыми по коду, список блокеров пересобран на реально открытые
(F-P1, pg_anonymizer, прод-`.env`). Правки только в `docs/`; код/схема/git не
затрагиваются.
```skills-json
[]
```
```steps-json
[
{"op":"Edit","object":"docs/security/2026-06-17-go-live-security-report.md","ref":"V3"},
{"op":"Edit","object":"docs/security/2026-06-17-open-items.md","ref":"V3"}
]
```
```verified-context-json
[
{"id":"vc1","kind":"EXTRACTED","ref":"app/routes/web.php","anchor":"Go-live: auth:sanctum"},
{"id":"vc2","kind":"EXTRACTED","ref":"docs/superpowers/specs/2026-06-17-audit-verify-correction-spec.md","anchor":"поправка аудита по факту кода"}
]
```
## Переговоры
### Круг 1
Две аддитивные правки документов `docs/`: в отчёт добавляется блок-поправка с
верифицированными по коду фактами (E9/E18 закрыты `auth:sanctum`, `/api/admin/*`
за `saas-admin` fail-closed, SSRF за `WebhookUrlGuard`), в трекере строки B3/B4/B5
переводятся в «закрыто фактом / остаток». Продуктовый код, схема, миграции, git не
затрагиваются; коммита нет. Навыки не нужны (skills-json пуст).
### Круг 2
Прошу наставника зафиксировать forward-`recommendation`: подтвердить, что поправка
должна сохранить вердикт NO-GO (реальные блокеры F-P1/pg_anonymizer/прод-`.env`
остаются) и не выдавать закрытые пункты за нерешённые.
@@ -0,0 +1,42 @@
# План v2 — прогон Semgrep после установки
## Цель
Запустить `npm run sast` (Semgrep, установлен) и обновить строку Semgrep в отчёте
go-live security gate реальным результатом. Цель — `main`, локально; продуктовый
код, схема, git не затрагиваются; правка только в файле отчёта.
```skills-json
[]
```
```steps-json
[
{"op":"Bash","object":"npm run sast","ref":"S1"},
{"op":"Edit","object":"docs/security/2026-06-17-go-live-security-report.md","ref":"S2"}
]
```
```verified-context-json
[
{"id":"vc1","kind":"EXTRACTED","ref":"docs/security/2026-06-17-go-live-security-report.md","anchor":"SECURITY GO-LIVE REPORT"},
{"id":"vc2","kind":"EXTRACTED","ref":"docs/superpowers/specs/2026-06-17-semgrep-run-spec.md","anchor":"прогон Semgrep после установки"}
]
```
## Переговоры
### Круг 1
Два шага: `npm run sast` (read-only статический анализ кода Semgrep'ом, ничего не
правит и наружу не шлёт) и Edit строки Semgrep в существующем отчёте `docs/security/`.
Продуктовый код, схема, миграции, git не затрагиваются; коммита нет. Навыки не
нужны (skills-json пуст). Semgrep установлен (semgrep-1.166.0).
### Круг 2
Прошу наставника зафиксировать forward-`recommendation` для этого плана: на что
обратить внимание при разборе вывода Semgrep — а именно, что находки severity
critical/high обязаны быть отражены в отчёте как блокеры (NO-GO), medium — как
предупреждения, low/info — справочно; и что строка Semgrep в отчёте должна
содержать фактическую severity-разбивку, а не остаться PENDING.
@@ -0,0 +1,34 @@
# План — прогон Semgrep после установки
## Цель
Запустить `npm run sast` (Semgrep) после установки и обновить строку Semgrep в
отчёте go-live security gate реальным результатом. Цель — `main`, локально;
продуктовый код, схема, git не затрагиваются; правка только в файле отчёта.
```skills-json
[]
```
```steps-json
[
{"op":"Bash","object":"npm run sast","ref":"S1"},
{"op":"Edit","object":"docs/security/2026-06-17-go-live-security-report.md","ref":"S2"}
]
```
```verified-context-json
[
{"id":"vc1","kind":"EXTRACTED","ref":"docs/security/2026-06-17-go-live-security-report.md","anchor":"SECURITY GO-LIVE REPORT"},
{"id":"vc2","kind":"EXTRACTED","ref":"docs/superpowers/specs/2026-06-17-semgrep-run-spec.md","anchor":"прогон Semgrep после установки"}
]
```
## Переговоры
### Круг 1
Два шага: `npm run sast` (read-only статический анализ кода Semgrep'ом, ничего не
правит и наружу не шлёт) и Edit строки Semgrep в существующем отчёте `docs/security/`.
Продуктовый код, схема, миграции, git не затрагиваются; коммита нет. Навыки не
нужны (skills-json пуст). Запуск — после завершения установки Semgrep в терминале.
@@ -0,0 +1,63 @@
# Спека — доведение аудита + трекер открытых вопросов
## Цель
Довести go-live security gate до конца в пределах того, что запускается без
доустановки: перезапустить статические проверки gitleaks и Ward правильными
командами, проверить готовность ZAP (наличие Java), обновить отчёт реальными
результатами, и зафиксировать все открытые вопросы сессии в отдельный файл-трекер.
Цель — текущая ветка `main`, локальный контур; код/схема/git не правятся.
## A1 — перезапуск статики gitleaks + Ward {#A1}
**Контракт.** gitleaks — полный скан истории на секреты; Ward — скан `app/`
позиционной командой (`./bin/ward.exe scan app/`, не устаревший `--path`).
Каждый результат заносится в отчёт по severity.
**Edge-case.** Если gitleaks уезжает в фоновый запуск и его вывод недоступен на
чтение — результат помечается как «получить из вывода» и не выдаётся за «чисто».
Учебная фикстура секрет-сканера — в allowlist gitleaks, не утечка.
**Конвенция.** Реальная утечка gitleaks = NO-GO (critical). Ward critical = NO-GO;
`APP_DEBUG`/`APP_ENV=local` на dev-`.env` — info (ожидаемо), на боевом — проверить.
**Критерий.** Оба инструмента отработали, результат разнесён по severity в отчёт.
## A2 — готовность ZAP {#A2}
**Контракт.** Проверить наличие Java (`java -version`) — ZAP требует Java 17+ и
демона. Если Java нет — ZAP остаётся `PENDING` с явной причиной, без ложного «ок».
**Критерий.** В отчёте — статус ZAP (PENDING + причина либо результат).
## A3 — обновление отчёта {#A3}
**Контракт.** Существующий отчёт `docs/security/2026-06-17-go-live-security-report.md`
обновляется реальными результатами gitleaks/Ward/ZAP-готовности; вердикт и списки
блокеров/предупреждений/PENDING пересобираются с учётом новых данных.
**Edge-case.** Semgrep остаётся PENDING (не установлен; установка под стеной
блокируется как floor-опасная) — фиксируется как требующий доустановки в терминале.
**Критерий.** Отчёт содержит обновлённые строки Шага 1 и пересобранный вердикт.
## A4 — трекер открытых вопросов {#A4}
**Контракт.** Новый файл `docs/security/2026-06-17-open-items.md` со всеми открытыми
вопросами сессии по категориям: блокеры безопасности (F-P1, pg_anonymizer, P0/P1
угрозы), незавершённые инструменты аудита (Semgrep, ZAP, и итог gitleaks/Ward),
находки осмотра портала (F3/F4/F5 — статус), баги машинерии стены (чтение/десинк/
недетерминизм судьи), хозяйственное (незакоммиченное, непушенное, нормативные хвосты).
**Конвенция.** Каждый пункт — приоритет, краткая суть, где правится, и пересекается
ли с зоной параллельной сессии (фронтенд `app/`).
**Критерий.** Файл существует, разбит по категориям, каждый пункт с приоритетом и
зоной правки.
```verified-context-json
[
{"id":"vc1","kind":"EXTRACTED","ref":"docs/security/2026-06-17-go-live-security-report.md","anchor":"SECURITY GO-LIVE REPORT"},
{"id":"vc2","kind":"EXTRACTED","ref":"docs/superpowers/2026-06-17-wall-read-block-bug.md","anchor":"чтение под стеной"}
]
```
@@ -0,0 +1,49 @@
# Спека — поправка аудита по факту кода (верификация маршрутов и SSRF)
## Цель
Внести в отчёт и трекер аудита поправку: три пункта, ранее помеченные как P0 «из
модели угроз, в коде не переподтверждено», проверены по реальному коду и оказались
закрытыми. Документы должны отражать факт, а не устаревшую STRIDE-карту. Правки —
только в `docs/`; код/схема/git не трогаются.
## V1 — что подтверждено фактом {#V1}
**Контракт.** По `app/routes/web.php` (актуальный код): `/api/dashboard/summary`,
`/api/managers`, `/api/lead-statuses` — под `auth:sanctum`(+`tenant`); группа
`/api/admin/*` — под middleware `saas-admin` (dev пропускает, prod fail-closed 503);
`/api/webhooks/test` — под `auth:sanctum + tenant`. По `WebhookSettingsController`:
`WebhookUrlGuard::blockReason()` вызывается на сохранении и на тесте, блокирует
cloud-metadata/loopback/RFC1918, URL обязан быть `https://`.
**Конвенция.** Закрытое в коде не остаётся в списке блокеров; переводится в
«закрыто фактом» либо в остаточный пункт (config/edge).
**Критерий.** В отчёте и трекере E9/E18/E20/SSRF помечены как закрытые/смягчённые
с указанием остатка.
## V2 — что остаётся реальным блокером {#V2}
**Контракт.** Реально открыты (подтверждено фактом): F-P1 (нет ретеншена телефонов
лидов — по модели `Deal` и схеме), pg_anonymizer (не установлен), APP_DEBUG/APP_ENV
на боевом `.env.production` (проверить). Остаточные не-блокеры: bypass-флаг и SSO
админки (config/Б-1), DNS-rebind на будущей доставке webhook (edge), rate-limit на
login (P1, проверить throttle-конфиг).
**Критерий.** Вердикт NO-GO сохраняется, но список блокеров пересобран на
подтверждённые пункты.
## V3 — где править {#V3}
**Контракт.** Поправка вносится в отчёт `docs/security/2026-06-17-go-live-security-report.md`
(блок вердикта/угроз) и в трекер `docs/security/2026-06-17-open-items.md` (строки
B3/B4/B5), аддитивно, без слома структуры.
**Критерий.** Оба файла обновлены, прежний текст не повреждён.
```verified-context-json
[
{"id":"vc1","kind":"EXTRACTED","ref":"app/routes/web.php","anchor":"Go-live: auth:sanctum"},
{"id":"vc2","kind":"EXTRACTED","ref":"app/app/Http/Controllers/Api/WebhookSettingsController.php","anchor":"WebhookUrlGuard::blockReason"}
]
```
@@ -0,0 +1,34 @@
# Спека — прогон Semgrep после установки
## Цель
Прогнать статический анализатор Semgrep (`npm run sast`) после его установки в
окружение и занести результат в отчёт go-live security gate. Цель — ветка `main`,
локально; код/схема/git не правятся, правка только в файле отчёта.
## S1 — прогон Semgrep {#S1}
**Контракт.** Запустить `npm run sast` (Semgrep с конфигами p/php, p/javascript,
p/typescript, p/secrets + локальный `.semgrep.yml`). Результат разнести по severity.
**Edge-case.** Если вывод уезжает в фоновый запуск и недоступен на чтение —
результат помечается «получить из вывода», не выдаётся за «чисто». Если Semgrep всё
ещё не на PATH — фиксируется, что установка не завершилась.
**Конвенция.** critical/high = NO-GO; medium = предупреждение; low/info — справочно.
**Критерий.** Semgrep отработал, результат по severity готов для отчёта.
## S2 — занести в отчёт {#S2}
**Контракт.** В отчёте `docs/security/2026-06-17-go-live-security-report.md` строка
Semgrep обновляется с PENDING на фактический результат (severity-разбивка).
**Критерий.** Строка Semgrep в отчёте отражает реальный прогон.
```verified-context-json
[
{"id":"vc1","kind":"EXTRACTED","ref":"docs/security/2026-06-17-go-live-security-report.md","anchor":"SECURITY GO-LIVE REPORT"},
{"id":"vc2","kind":"EXTRACTED","ref":"docs/security/2026-06-17-open-items.md","anchor":"Открытые вопросы"}
]
```