Failure scenario
- Register a webhook URL whose initial public address passes
is_url_blocked().
- Have that endpoint answer with a
307 or 308 redirect to a loopback, private-network, or cloud-metadata destination.
- Dispatch any subscribed event.
Registration validates only the original URL. Delivery uses aiohttp's redirect-following default, so the redirected destination is never checked and the POST (including its event body and signature header) is resent to an address the registration guard explicitly intends to block. A hostname that changes DNS answers between registration and delivery creates the same bypass because no resolved address is pinned or revalidated.
Sites
src/notifications/outbound_webhooks.py:223-247 validates only the registration-time URL.
src/notifications/outbound_webhooks.py:297-313 does the same only when an updated URL is stored.
src/notifications/outbound_webhooks.py:368-379 posts through the default redirect/DNS behavior without per-hop destination validation.
src/tools/safe_fetch.py:202-279 already contains the repository's manual per-hop validation and DNS-pinning transport pattern; the webhook path bypasses it.
Expected result
Use an SSRF-safe delivery transport that disables automatic redirects, validates and pins every hop at request time, and strips sensitive headers on any permitted cross-origin redirect.
Failure scenario
is_url_blocked().307or308redirect to a loopback, private-network, or cloud-metadata destination.Registration validates only the original URL. Delivery uses
aiohttp's redirect-following default, so the redirected destination is never checked and the POST (including its event body and signature header) is resent to an address the registration guard explicitly intends to block. A hostname that changes DNS answers between registration and delivery creates the same bypass because no resolved address is pinned or revalidated.Sites
src/notifications/outbound_webhooks.py:223-247validates only the registration-time URL.src/notifications/outbound_webhooks.py:297-313does the same only when an updated URL is stored.src/notifications/outbound_webhooks.py:368-379posts through the default redirect/DNS behavior without per-hop destination validation.src/tools/safe_fetch.py:202-279already contains the repository's manual per-hop validation and DNS-pinning transport pattern; the webhook path bypasses it.Expected result
Use an SSRF-safe delivery transport that disables automatic redirects, validates and pins every hop at request time, and strips sensitive headers on any permitted cross-origin redirect.