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
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.
Built-in blocklist extended to cover the ranges above, in both IPv4 and IPv6 forms.
Enforcement stays in ssrfSafeDialer (the authoritative check), not only in validateURL, so the rebinding protection is preserved.
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.
A test that an operator-supplied blocked_networks entry is actually enforced by the dialer.
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.
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
Add a blocked_networks config key: a YAML list of CIDRs parsed with net/netip. Strict — an invalid CIDR aborts startup naming the key and the offending value. Operator prefixes are added to the built-in defaults, which stay enforced (a merge, not a replacement).
Extend the built-in blocklist to CGNAT 100.64.0.0/10, IETF protocol assignments 192.0.0.0/24, benchmark 198.18.0.0/15, and NAT64 64:ff9b::/96, covering IPv4-mapped-IPv6 forms.
Enforcement stays in the dial-time SSRF re-resolution (the authoritative check that closes the DNS-rebinding window), which now also consults the operator prefixes.
Failing table-driven tests first, over address families and ranges: each newly blocked range, ::ffff:10.0.0.1, 169.254.169.254, a public control, and an operator-supplied entry enforced by the dialer.
Document the key in README.md and config.example.yml.
Note: the issue lists 192.0.0.0/24 (IETF protocol assignments). Tests added to next today use 192.0.2.10 (TEST-NET-1, 192.0.2.0/24) as a public upstream — a disjoint range — so TEST-NET-1 stays dialable and those tests are left untouched.
Model: opus-4-8
Plan for https://git.eeqj.de/sneak/pixa/issues/67
- Add a `blocked_networks` config key: a YAML list of CIDRs parsed with `net/netip`. Strict — an invalid CIDR aborts startup naming the key and the offending value. Operator prefixes are added to the built-in defaults, which stay enforced (a merge, not a replacement).
- Extend the built-in blocklist to CGNAT `100.64.0.0/10`, IETF protocol assignments `192.0.0.0/24`, benchmark `198.18.0.0/15`, and NAT64 `64:ff9b::/96`, covering IPv4-mapped-IPv6 forms.
- Enforcement stays in the dial-time SSRF re-resolution (the authoritative check that closes the DNS-rebinding window), which now also consults the operator prefixes.
- Failing table-driven tests first, over address families and ranges: each newly blocked range, `::ffff:10.0.0.1`, `169.254.169.254`, a public control, and an operator-supplied entry enforced by the dialer.
- Document the key in `README.md` and `config.example.yml`.
Note: the issue lists `192.0.0.0/24` (IETF protocol assignments). Tests added to `next` today use `192.0.2.10` (TEST-NET-1, `192.0.2.0/24`) as a public upstream — a disjoint range — so TEST-NET-1 stays dialable and those tests are left untouched.
Model: opus-4-8
Added a blocked_networks config key: a list of CIDRs parsed with net/netip, added to the built-in SSRF blocklist rather than replacing it; an invalid CIDR aborts startup naming the key and the offending value. The built-in ranges now also cover CGNAT 100.64.0.0/10, IETF protocol assignments 192.0.0.0/24, benchmark 198.18.0.0/15, and NAT64 64:ff9b::/96, with IPv4-mapped-IPv6 forms unmapped before matching. Enforcement stays in the dial-time re-resolution, which now also consults the operator-supplied prefixes. Documented in README.md and config.example.yml.
On the range you flagged as uncertain: ::ffff:10.0.0.1 is blocked (a test pins it), and 169.254.169.254 is already covered by the link-local check (also pinned).
Judgement call: the issue lists 192.0.0.0/24, while the Fetch tests added to next today use 192.0.2.10 (TEST-NET-1, 192.0.2.0/24) as a public upstream. Those are disjoint ranges, so TEST-NET-1 stays dialable and those tests were left unchanged.
Model: opus-4-8
Implemented in https://git.eeqj.de/sneak/pixa/pulls/124 (base `next`).
Added a `blocked_networks` config key: a list of CIDRs parsed with `net/netip`, added to the built-in SSRF blocklist rather than replacing it; an invalid CIDR aborts startup naming the key and the offending value. The built-in ranges now also cover CGNAT `100.64.0.0/10`, IETF protocol assignments `192.0.0.0/24`, benchmark `198.18.0.0/15`, and NAT64 `64:ff9b::/96`, with IPv4-mapped-IPv6 forms unmapped before matching. Enforcement stays in the dial-time re-resolution, which now also consults the operator-supplied prefixes. Documented in `README.md` and `config.example.yml`.
On the range you flagged as uncertain: `::ffff:10.0.0.1` is blocked (a test pins it), and `169.254.169.254` is already covered by the link-local check (also pinned).
Judgement call: the issue lists `192.0.0.0/24`, while the Fetch tests added to `next` today use `192.0.2.10` (TEST-NET-1, `192.0.2.0/24`) as a public upstream. Those are disjoint ranges, so TEST-NET-1 stays dialable and those tests were left unchanged.
Model: opus-4-8
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Verified against
mainat61f42e6. This is the "implement blocked networks configuration to extend SSRF protection" P1 item inTODO.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, andssrfSafeDialer(: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 infrastructure192.0.0.0/24— IETF protocol assignments198.18.0.0/15— benchmark/test range64:ff9b::/96— NAT64, which maps straight back onto IPv4 private spaceUncertain, verify before changing: IPv4-mapped IPv6 (
::ffff:10.0.0.1) may already be handled, because Go'sip.IsPrivate()callsTo4()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
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.ssrfSafeDialer(the authoritative check), not only invalidateURL, so the rebinding protection is preserved.::ffff:10.0.0.1,169.254.169.254, and a positive case proving ordinary public addresses still resolve and dial.blocked_networksentry is actually enforced by the dialer.make checkgreen.Related
Redirect-based SSRF (the
CheckRedirectvalidator athttpfetcher.go:133-143) is currently untested — covered in the separate httpfetcher test-coverage issue, but worth a glance while in this code.Plan for #67
blocked_networksconfig key: a YAML list of CIDRs parsed withnet/netip. Strict — an invalid CIDR aborts startup naming the key and the offending value. Operator prefixes are added to the built-in defaults, which stay enforced (a merge, not a replacement).100.64.0.0/10, IETF protocol assignments192.0.0.0/24, benchmark198.18.0.0/15, and NAT6464:ff9b::/96, covering IPv4-mapped-IPv6 forms.::ffff:10.0.0.1,169.254.169.254, a public control, and an operator-supplied entry enforced by the dialer.README.mdandconfig.example.yml.Note: the issue lists
192.0.0.0/24(IETF protocol assignments). Tests added tonexttoday use192.0.2.10(TEST-NET-1,192.0.2.0/24) as a public upstream — a disjoint range — so TEST-NET-1 stays dialable and those tests are left untouched.Model: opus-4-8
Implemented in #124 (base
next).Added a
blocked_networksconfig key: a list of CIDRs parsed withnet/netip, added to the built-in SSRF blocklist rather than replacing it; an invalid CIDR aborts startup naming the key and the offending value. The built-in ranges now also cover CGNAT100.64.0.0/10, IETF protocol assignments192.0.0.0/24, benchmark198.18.0.0/15, and NAT6464:ff9b::/96, with IPv4-mapped-IPv6 forms unmapped before matching. Enforcement stays in the dial-time re-resolution, which now also consults the operator-supplied prefixes. Documented inREADME.mdandconfig.example.yml.On the range you flagged as uncertain:
::ffff:10.0.0.1is blocked (a test pins it), and169.254.169.254is already covered by the link-local check (also pinned).Judgement call: the issue lists
192.0.0.0/24, while the Fetch tests added tonexttoday use192.0.2.10(TEST-NET-1,192.0.2.0/24) as a public upstream. Those are disjoint ranges, so TEST-NET-1 stays dialable and those tests were left unchanged.Model: opus-4-8
clawbot referenced this issue2026-09-22 00:46:43 +02:00