# План: усиление webhook — DNS-rebind пиннинг + аддитивный HMAC

## Цель

Через TDD реализовать две webhook-правки: (А) `WebhookUrlGuard::safeDeliveryIp`
+ пиннинг соединения к проверенному IP в `WebhookSettingsController::test`;
(Б) `verifyHmac` + secretless-маршрут в `SupplierWebhookController` / `web.php`.
Сначала падающий Pest-файл, затем реализация, затем прогон всей webhook-сюиты.
Прогон — из каталога `app`.

```skills-json
["test-driven-development"]
```

```steps-json
[
  {"op": "Bash", "object": "cd \"c:/моя/проекты/портал crm/Документация/app\"", "ref": "D5"},
  {"op": "Write", "object": "c:/моя/проекты/портал crm/Документация/app/tests/Feature/Http/Webhook/WebhookHardeningTest.php", "ref": "D5"},
  {"op": "Bash", "object": "php artisan test tests/Feature/Http/Webhook/WebhookHardeningTest.php", "ref": "D5"},
  {"op": "Edit", "object": "c:/моя/проекты/портал crm/Документация/app/app/Support/WebhookUrlGuard.php", "ref": "D1"},
  {"op": "Edit", "object": "c:/моя/проекты/портал crm/Документация/app/app/Http/Controllers/Api/SupplierWebhookController.php", "ref": "D2"},
  {"op": "Edit", "object": "c:/моя/проекты/портал crm/Документация/app/app/Http/Controllers/Api/WebhookSettingsController.php", "ref": "D1"},
  {"op": "Edit", "object": "c:/моя/проекты/портал crm/Документация/app/routes/web.php", "ref": "D2"},
  {"op": "Bash", "object": "php artisan test --filter=Webhook", "ref": "D5"},
  {"op": "Write", "object": "c:/моя/проекты/портал crm/Документация/docs/superpowers/runbooks/2026-06-17-webhook-hardening.md", "ref": "D2"}
]
```

```verified-context-json
[
  {
    "id": "ctx-guard",
    "kind": "EXTRACTED",
    "ref": "app/app/Support/WebhookUrlGuard.php",
    "anchor": "public static function blockReason(string $url): ?string"
  }
]
```

## Переговоры

### Круг 1

Довод по реализации: обе правки аддитивны. Часть А — единый резолв на блок-решение
и пиннинг закрывает DNS-rebind TOCTOU; `blockReason` не трогаем (используется на
сохранении). Часть Б — HMAC принимается как альтернатива URL-секрету, старый
`{secret}`-маршрут сохраняется, поэтому существующие supplier-тесты не ломаются;
финальный шаг прогоняет всю webhook-сюиту (`--filter=Webhook`). Тест на
`safeDeliveryIp` — на IP-литералах (детерминированно, без DNS); HMAC-тест шлёт
сырое тело через `call()`. Прошу наставника зафиксировать forward-рекомендацию по
порядку вывода URL-секрета после миграции поставщика.
