# План: доменный go-live аудит (повтор C) — карточки + фактические находки + подтверждающие greps

## Цель

Записать **промежуточный неавторизующий** отчёт доменного аудита по спеке
`2026-06-15-go-live-security-audit-evidence-2.md` (раздел D5). Находки уже получены чтением
артефактов (ниже, с файлом+строкой); grep-шаги их **подтверждают**. Поля находки: ID;
инвариант (D1–D4); `файл:строка`; приоритет `P1`/`P2`/`P3`; статус; рекомендация. Блокер =
открытая `P1`. Форма — не авторизация go-live; финал go/no-go за владельцем. Открытые
вопросы (Б-1/SEC/CTO/Биз/Диз/DO) не закрываются.

## КАРТОЧКИ НАВЫКОВ (верифицированные — дословно из `docs/registry/contracts/`)

```json
[
  {"skill":"threat-model","kind":"own","needs":["портал для моделирования угроз перед публикацией"],"produces":["STRIDE-модель: attack surface + приоритеты защиты"],"constraints":["self-authored; STRIDE going-public","ADR-014 IS6 НЕ Trail of Bits #39"],"preview-form":"outline","defaults":["карта точек входа → приоритизация по STRIDE"],"key-decisions":["что защищать первым перед публикацией"],"acceptance-criteria":["attack surface картирован, защита приоритизирована"]},
  {"skill":"pdn-152fz-audit","kind":"own","needs":["задача аудита ПДн / 152-ФЗ"],"produces":["аудит ПДн: инвентаризация, согласия, маскирование, логи доступа, pd_subject_request"],"constraints":["self-authored; режим техника + закон","ADR-014: IS4 НЕ pg_anonymizer #29, IS5 НЕ D2"],"preview-form":"outline","defaults":["инвентаризация ПДн в схеме/коде → проверка соответствия"],"key-decisions":["режим: техника или закон"],"acceptance-criteria":["ПДн инвентаризированы, соответствие 152-ФЗ проверено"]},
  {"skill":"billing-audit","kind":"own","needs":["код/диф биллинга для аудита денежной корректности"],"produces":["отчёт о money-инвариантах биллинга"],"constraints":["self-authored; аудит кода биллинга","ADR-012: НЕ налоги, НЕ процесс, НЕ security"],"preview-form":"outline","defaults":["проверять потерю копеек, двойное списание, tier-резолюцию, дрейф reconcile"],"key-decisions":["какие денежные инварианты в scope"],"acceptance-criteria":["каждый денежный путь проверен на потерю копеек и двойное списание"]},
  {"skill":"security-go-live","kind":"own","needs":["портал перед выходом в интернет (go/no-go)"],"produces":["вердикт GO/NO-GO + сводка проверок безопасности"],"constraints":["self-authored оркестратор #68-72 + D3","ADR-014 IS7 НЕ audit-portal"],"preview-form":"outline","defaults":["оркеструет ZAP/Nuclei/Ward/pdn-152fz/threat-model + Semgrep"],"key-decisions":["go/no-go порог"],"acceptance-criteria":["вердикт GO/NO-GO обоснован результатами проверок"]}
]
```

## Фактические находки (получены чтением артефактов)

| ID | инвариант | файл:строка | приоритет | статус | рекомендация |
|----|-----------|-------------|-----------|--------|--------------|
| F-T2 | D1 | app/app/Http/Middleware/EnsureSaasAdmin.php:34 | P1 | открыт | вернуть app-гейт; `admin_user_id` из сессии, не из `$request->input` (ResolvesAdminUserId.php:24) |
| F-P1 | D3 | app/app/Services/Pd/PdErasureService.php:167 | P1 | открыт | анонимизировать `deals.phones[]` (schema.sql:1632) |
| F-T1 | D2 | app/app/Http/Controllers/Api/WebhookSettingsController.php:150 | P2 | остаточный риск | `withoutRedirecting()`; DNS-rebinding → accepted-risk |
| F-T3 | D2 | app/app/Http/Controllers/Api/SupplierWebhookController.php:162 | P3 | остаточный риск | секрет не в URL / body-HMAC |
| F-P2 | D3 | нет config/sentry.php | P3 | остаточный риск | `send_default_pii=false` + before_send |
| F-B1 | D4 | db/schema.sql:1090 | P3 | остаточный риск | UNIQUE на `lead_charges` |

**Чисто:** деньги bcmath (LedgerService.php:72-84), идемпотентность `lockForUpdate`+PK `supplier_lead_deliveries`, логи без ПДн, `WebhookUrlGuard.blockReason()` добротен, маршруты под auth.

## Маппинг «непустой вывод grep = подтверждение находки»

- `api/admin` в web.php непусто → админ-маршруты под группой saas-admin (контекст F-T2).
- `next` в EnsureSaasAdmin непусто и это `return $next` без проверки → подтверждает F-T2 (no-op гейт).
- `admin_user_id` в ResolvesAdminUserId непусто (из `$request->input`) → F-T2 (audit-trail подделываем).
- `Http::timeout` в WebhookSettingsController непусто без `withoutRedirecting` рядом → F-T1.
- `blockReason` в WebhookUrlGuard непусто → guard существует (митигатор F-T1).
- `contact_name` в PdErasureService непусто, но `phones` рядом нет → F-P1 (deals.phones не чистится).
- `phones` в schema.sql непусто (`phones JSONB`) → подтверждает наличие непокрытого массива (F-P1).
- `bcsub` в LedgerService непусто → bcmath применяется (домен D4 чист по float).
- `lead_charges` в schema.sql: индекс не UNIQUE → F-B1.

Пустой вывод любого grep → в отчёте «артефакт не подтвердил находку» (fallback).

## Порядок исполнения

Под печатью до Write вызываются 4 объявленных скила (threat-model → pdn-152fz-audit →
billing-audit → security-go-live). 9 grep-шагов подтверждают находки выше. Write агрегирует
фактические находки в отчёт по полям D5.

```skills-json
["threat-model", "pdn-152fz-audit", "billing-audit", "security-go-live"]
```

```steps-json
[
  {"op":"Bash","object":"grep -n api/admin app/routes/web.php","ref":"D1"},
  {"op":"Bash","object":"grep -n next app/app/Http/Middleware/EnsureSaasAdmin.php","ref":"D1"},
  {"op":"Bash","object":"grep -n admin_user_id app/app/Http/Controllers/Concerns/ResolvesAdminUserId.php","ref":"D1"},
  {"op":"Bash","object":"grep -n Http::timeout app/app/Http/Controllers/Api/WebhookSettingsController.php","ref":"D2"},
  {"op":"Bash","object":"grep -n blockReason app/app/Support/WebhookUrlGuard.php","ref":"D2"},
  {"op":"Bash","object":"grep -n contact_name app/app/Services/Pd/PdErasureService.php","ref":"D3"},
  {"op":"Bash","object":"grep -n phones db/schema.sql","ref":"D3"},
  {"op":"Bash","object":"grep -n bcsub app/app/Services/Billing/LedgerService.php","ref":"D4"},
  {"op":"Bash","object":"grep -n lead_charges db/schema.sql","ref":"D4"},
  {"op":"Write","object":"docs/superpowers/audits/2026-06-15-portal-domain-audit-findings.md","ref":"D5"}
]
```

```verified-context-json
[
  {"id":"D1a","kind":"EXTRACTED","ref":"app/app/Http/Middleware/EnsureSaasAdmin.php","anchor":"return $next($request);"},
  {"id":"D1b","kind":"EXTRACTED","ref":"app/app/Http/Controllers/Concerns/ResolvesAdminUserId.php","anchor":"$request->input('admin_user_id')"},
  {"id":"D3a","kind":"EXTRACTED","ref":"app/app/Services/Pd/PdErasureService.php","anchor":"'contact_name' => 'Удалено'"},
  {"id":"D3b","kind":"EXTRACTED","ref":"db/schema.sql","anchor":"phones          JSONB"},
  {"id":"D4a","kind":"EXTRACTED","ref":"app/app/Services/Billing/LedgerService.php","anchor":"bcsub((string) $lockedTenant->balance_rub"}
]
```
