9.2 KiB
План: доменный go-live аудит (повтор E) — карточки + кандидаты-находки + дискриминирующие greps
Цель
Записать промежуточный неавторизующий отчёт доменного аудита по спеке
2026-06-15-go-live-security-audit-evidence-3.md (раздел D5) как выходной артефакт
(Write-шаг). Находки ниже — кандидаты-гипотезы, которые подтверждаются grep-шагами;
финальный статус каждой фиксируется только в записанном отчёте (Write), не в плане. Поля:
ID; инвариант (D1–D4); файл:строка; приоритет; статус; рекомендация. Блокер = открытая
P1. Форма — не авторизация go-live; финал go/no-go за владельцем. Открытые вопросы
(Б-1/SEC/CTO/Биз/Диз/DO) не закрываются.
security-go-live в этом прогоне выносит промежуточный статус «готов/не готов + блокеры»
(частный случай его контракта GO/NO-GO под ограничение цели D5 — авторизации нет).
КАРТОЧКИ НАВЫКОВ (верифицированные — дословно из docs/registry/contracts/)
[
{"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 обоснован результатами проверок"]}
]
Находки-кандидаты (подтверждаются grep-шагами; финальный статус — в записанном отчёте)
| ID | инвариант | файл:строка | приоритет | статус-гипотеза | подтверждающий grep |
|---|---|---|---|---|---|
| F-T2 | D1 | EnsureSaasAdmin.php:34 + ResolvesAdminUserId.php:24 (оба) | P1 | открыт | grep next (no-op) + grep admin_user_id (из $request->input) |
| F-P1 | D3 | PdErasureService.php:167 + schema.sql phones JSONB |
P1 | открыт | grep contact_name (deals без phones) + grep phones |
| F-T1 | D2 | WebhookSettingsController.php:150 | P2 | остаточный риск | grep withoutRedirecting = пусто → редирект не отключён |
| F-T3 | D2 | SupplierWebhookController.php (verifyIpAllowlist) | P3 | остаточный риск | grep подтверждает наличие приёмника |
| F-P2 | D3 | отсутствие config/sentry.php | P3 | остаточный риск | observation (файла нет — extracted-якоря нет) |
| F-B1 | D4 | schema.sql index lead_charges_deal_id… |
P3 | остаточный риск | grep имени индекса = CREATE INDEX (не UNIQUE) |
Чисто (контр-гипотезы подтверждены): деньги bcmath (grep bcsub), идемпотентность lockForUpdate+PK supplier_lead_deliveries, логи без ПДн, WebhookUrlGuard.blockReason добротен.
Критерий интерпретации grep и fallback
- F-T2: grep
nextнепусто и тело =return $nextбез проверки → no-op гейт; grepadmin_user_idнепусто (из$request->input) → audit-trail подделываем. - F-T1: grep
withoutRedirectingпусто → флаг не выставлен → Guzzle идёт за редиректами (риск подтверждён). - F-B1: grep имени индекса даёт
CREATE INDEX(неCREATE UNIQUE INDEX) → не-UNIQUE подтверждён. - fallback: если ожидаемый вывод grep не совпал (напр.
withoutRedirectingНЕ пусто, или индекс оказался UNIQUE) → находка в отчёте помечается «не подтверждена — requires-manual-check», НЕ удаляется молча и НЕ выдаётся за факт.
Порядок исполнения
Под печатью до Write вызываются 4 объявленных скила (threat-model → pdn-152fz-audit → billing-audit → security-go-live). 10 grep-шагов производят дискриминирующий вывод по каждой находке. Write агрегирует подтверждённые находки в отчёт-артефакт по D5.
["threat-model", "pdn-152fz-audit", "billing-audit", "security-go-live"]
[
{"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 withoutRedirecting 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_deal_id db/schema.sql","ref":"D4"},
{"op":"Write","object":"docs/superpowers/audits/2026-06-15-portal-domain-audit-findings.md","ref":"D5"}
]
[
{"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":"D2b","kind":"EXTRACTED","ref":"app/app/Http/Controllers/Api/WebhookSettingsController.php","anchor":"->post($sub->target_url, $testPayload)"},
{"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":"D3f","kind":"EXTRACTED","ref":"app/app/Http/Controllers/Api/SupplierWebhookController.php","anchor":"verifyIpAllowlist"},
{"id":"D4a","kind":"EXTRACTED","ref":"app/app/Services/Billing/LedgerService.php","anchor":"bcsub((string) $lockedTenant->balance_rub"},
{"id":"D4b","kind":"EXTRACTED","ref":"db/schema.sql","anchor":"lead_charges_deal_id_deal_received_at_index"}
]