fix: harden server-initiated HTTP against SSRF and DNS rebinding

Route all server-initiated outbound HTTP through one SSRF-protected layer:

- Resolve each host once, validate every resolved address (private,
  loopback, link-local, CGNAT 100.64/10, multicast, reserved, unspecified,
  IPv4-mapped IPv6), and reject if any is unsafe.
- Pin the connection to the validated address via curl's RESOLVE option so
  curl cannot re-resolve to a rebound IP (closes the validate-then-connect
  TOCTOU). Uses getaddrinfo so IPv6 records are checked too.
- Add a sync SafeTransport and safehttp.post, and move the webhook and
  recipe-action paths off raw requests.post so they are guarded like the
  scraper.
- Add HTTP_ALLOW_LIST / HTTP_DISALLOW_LIST settings (deny > allow > default)
  so operators can reach an internal server or block extra hosts.

Apprise notifications egress through the third-party library and are not
covered here.
This commit is contained in:
Hayden
2026-07-19 21:41:30 -05:00
parent db4262dc67
commit ce88ed6841
11 changed files with 435 additions and 57 deletions

View File

@@ -30,6 +30,8 @@
| SECURITY_MAX_LOGIN_ATTEMPTS | 5 | Maximum times a user can provide an invalid password before their account is locked |
| SECURITY_USER_LOCKOUT_TIME | 24 | Time in hours for how long a users account is locked |
| ALLOWED_IFRAME_HOSTS | `""` | Comma-separated extra hostnames allowed as `<iframe>` sources in recipe content. Extends the built-in list of trusted video providers (YouTube, Vimeo). Subdomains are included automatically. Only `https` sources are permitted. Adding hosts here opts into rendering embeds from those origins to all viewers, including the public, so add only origins you trust. |
| HTTP_ALLOW_LIST | `""` | Comma-separated hosts or CIDRs that server-initiated requests (recipe scraping, webhooks, recipe actions) may reach even when they resolve to an otherwise-blocked private/internal address. Use to allow a known internal server. |
| HTTP_DISALLOW_LIST | `""` | Comma-separated hosts or CIDRs that server-initiated requests may never reach, even if public. Takes precedence over `HTTP_ALLOW_LIST`. |
### Database