P1: blocked networks config + SSRF gaps (CGNAT, benchmark, NAT64 ranges not blocked) #67

Open
opened 2026-08-09 03:44:32 +02:00 by clawbot · 0 comments
Collaborator

Verified against main at 61f42e6. This is the "implement blocked networks configuration to extend SSRF protection" P1 item in TODO.md; confirmed genuinely still missing — there is no such config key and no CIDR list anywhere.

The existing protection is sound in structure: validateURL (internal/httpfetcher/httpfetcher.go:322-331) resolves and checks, and ssrfSafeDialer (:412-441) resolves again and re-checks immediately before dialing, so the DNS-rebinding window is correctly closed at the dialer — that part is good and should not be regressed.

The gap is coverage of isPrivateIP (:366-409). It relies on a hardcoded list and does not appear to cover:

  • 100.64.0.0/10 — CGNAT / carrier-grade NAT, routinely used for internal infrastructure
  • 192.0.0.0/24 — IETF protocol assignments
  • 198.18.0.0/15 — benchmark/test range
  • 64:ff9b::/96 — NAT64, which maps straight back onto IPv4 private space

Uncertain, verify before changing: IPv4-mapped IPv6 (::ffff:10.0.0.1) may already be handled, because Go's ip.IsPrivate() calls To4() internally. Confirm with a test rather than assuming in either direction.

Cloud metadata endpoints (169.254.169.254) fall under link-local — confirm that is actually covered, since it is the single highest-value SSRF target in any deployment on a cloud provider.

Definition of done

  1. A blocked_networks (or similarly named, following the descriptive-naming rule) config key accepting a list of CIDRs, merged with the built-in defaults rather than replacing them. Strict parsing: an invalid CIDR aborts startup naming the key and the offending value, per repo policy.
  2. Built-in blocklist extended to cover the ranges above, in both IPv4 and IPv6 forms.
  3. Enforcement stays in ssrfSafeDialer (the authoritative check), not only in validateURL, so the rebinding protection is preserved.
  4. Failing tests first, table-driven over the address families and ranges, including: each newly blocked range, ::ffff:10.0.0.1, 169.254.169.254, and a positive case proving ordinary public addresses still resolve and dial.
  5. A test that an operator-supplied blocked_networks entry is actually enforced by the dialer.
  6. make check green.

Redirect-based SSRF (the CheckRedirect validator at httpfetcher.go:133-143) is currently untested — covered in the separate httpfetcher test-coverage issue, but worth a glance while in this code.

Verified against `main` at `61f42e6`. This is the "implement blocked networks configuration to extend SSRF protection" P1 item in `TODO.md`; confirmed genuinely still missing — there is no such config key and no CIDR list anywhere. The existing protection is sound in structure: `validateURL` (`internal/httpfetcher/httpfetcher.go:322-331`) resolves and checks, and `ssrfSafeDialer` (`:412-441`) resolves **again** and re-checks immediately before dialing, so the DNS-rebinding window is correctly closed at the dialer — that part is good and should not be regressed. The gap is coverage of `isPrivateIP` (`:366-409`). It relies on a hardcoded list and does not appear to cover: - `100.64.0.0/10` — CGNAT / carrier-grade NAT, routinely used for internal infrastructure - `192.0.0.0/24` — IETF protocol assignments - `198.18.0.0/15` — benchmark/test range - `64:ff9b::/96` — NAT64, which maps straight back onto IPv4 private space **Uncertain, verify before changing:** IPv4-mapped IPv6 (`::ffff:10.0.0.1`) may already be handled, because Go's `ip.IsPrivate()` calls `To4()` internally. Confirm with a test rather than assuming in either direction. Cloud metadata endpoints (`169.254.169.254`) fall under link-local — confirm that is actually covered, since it is the single highest-value SSRF target in any deployment on a cloud provider. ## Definition of done 1. A `blocked_networks` (or similarly named, following the descriptive-naming rule) config key accepting a list of CIDRs, merged with the built-in defaults rather than replacing them. Strict parsing: an invalid CIDR aborts startup naming the key and the offending value, per repo policy. 2. Built-in blocklist extended to cover the ranges above, in both IPv4 and IPv6 forms. 3. Enforcement stays in `ssrfSafeDialer` (the authoritative check), not only in `validateURL`, so the rebinding protection is preserved. 4. Failing tests first, table-driven over the address families and ranges, including: each newly blocked range, `::ffff:10.0.0.1`, `169.254.169.254`, and a positive case proving ordinary public addresses still resolve and dial. 5. A test that an operator-supplied `blocked_networks` entry is actually enforced by the dialer. 6. `make check` green. ## Related Redirect-based SSRF (the `CheckRedirect` validator at `httpfetcher.go:133-143`) is currently untested — covered in the separate httpfetcher test-coverage issue, but worth a glance while in this code.
clawbot added this to the 1.0.0 milestone 2026-08-09 03:44:32 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/pixa#67