The SSRF guard lets a delivery reach [::], which is the host itself on Linux #341

Closed
opened 2026-09-29 11:10:38 +02:00 by clawbot · 2 comments
Collaborator

Found by the review of #339 (#339 (comment)). Out of that PR's scope, since it changes the list rather than documents it.

The SSRF guard's lists in internal/delivery/ssrf.go (blockedNetworks and alwaysBlockedNetworks) do not cover the IPv6 unspecified address ::. On Linux a connection to [::] reaches the host's own loopback, so a delivery target such as http://[::]:8080/ passes the guard with no allowlist set. IPv6 multicast (ff00::/8) and documentation space (2001:db8::/32) are not listed either. The IPv4 unspecified address is worth checking at the same time.

Definition of done

  • ::/128 is refused wherever 0.0.0.0 is today, or in alwaysBlockedNetworks if it can reach loopback, because the allowlist must never reopen loopback.
  • ff00::/8 and 2001:db8::/32 are refused by default.
  • Each entry has a one-line comment naming what it is.
  • Tests:
    • each address is refused at target creation and at delivery, with no allowlist set;
    • each fails when its entry is removed.
  • The README's blocklist description matches the list.

Model: opus-5-5

Found by the review of https://git.eeqj.de/sneak/webhooker/pulls/339 (https://git.eeqj.de/sneak/webhooker/pulls/339#issuecomment-106662). Out of that PR's scope, since it changes the list rather than documents it. The SSRF guard's lists in `internal/delivery/ssrf.go` (`blockedNetworks` and `alwaysBlockedNetworks`) do not cover the IPv6 unspecified address `::`. On Linux a connection to `[::]` reaches the host's own loopback, so a delivery target such as `http://[::]:8080/` passes the guard with no allowlist set. IPv6 multicast (`ff00::/8`) and documentation space (`2001:db8::/32`) are not listed either. The IPv4 unspecified address is worth checking at the same time. ## Definition of done - `::/128` is refused wherever `0.0.0.0` is today, or in `alwaysBlockedNetworks` if it can reach loopback, because the allowlist must never reopen loopback. - `ff00::/8` and `2001:db8::/32` are refused by default. - Each entry has a one-line comment naming what it is. - Tests: - each address is refused at target creation and at delivery, with no allowlist set; - each fails when its entry is removed. - The README's blocklist description matches the list. Model: opus-5-5
clawbot self-assigned this 2026-09-29 11:10:38 +02:00
Author
Collaborator

Plan. The issue body is the brief; #407 has since split the default blocklist into its private-and-reserved list (blockedNetworks), its public list (blockedPublicNetworks) and the unconditional list (alwaysBlockedNetworks).

  • ::/128 goes where the issue says: in alwaysBlockedNetworks if, as on Linux, a connection to it reaches the host's loopback, so the allowlist can never reopen it; 0.0.0.0 is checked the same way and moved there if it is not already refused unconditionally.
  • ff00::/8 (IPv6 multicast) and 2001:db8::/32 (IPv6 documentation) go in blockedNetworks as reserved space, each with a one-line comment.
  • 407's pinning test pins the new entries in their lists; new tests refuse each address at target creation and at delivery, and fail when its entry is removed.
  • The README's blocklist description (from #244) and the rules above each list are updated to match.

Model: opus-5-5

Plan. The issue body is the brief; https://git.eeqj.de/sneak/webhooker/pulls/407 has since split the default blocklist into its private-and-reserved list (`blockedNetworks`), its public list (`blockedPublicNetworks`) and the unconditional list (`alwaysBlockedNetworks`). - `::/128` goes where the issue says: in `alwaysBlockedNetworks` if, as on Linux, a connection to it reaches the host's loopback, so the allowlist can never reopen it; `0.0.0.0` is checked the same way and moved there if it is not already refused unconditionally. - `ff00::/8` (IPv6 multicast) and `2001:db8::/32` (IPv6 documentation) go in `blockedNetworks` as reserved space, each with a one-line comment. - 407's pinning test pins the new entries in their lists; new tests refuse each address at target creation and at delivery, and fail when its entry is removed. - The README's blocklist description (from https://git.eeqj.de/sneak/webhooker/issues/244) and the rules above each list are updated to match. Model: opus-5-5
Author
Collaborator

Done in #422.

On this host a connection to [::] reaches a listener on ::1, and one to 0.0.0.0 reaches a listener on 127.0.0.1; other 0.0.0.0/8 addresses reach nothing local. So 0.0.0.0/32 and ::/128 are now in alwaysBlockedNetworks, where no allowlist opens them, and ::/128 also joins the default blocklist beside 0.0.0.0/8. ff00::/8 and 2001:db8::/32 are refused by default and can be reopened. Every default blocklist entry has a one-line comment, the pinning tests cover each list's new entries, a new test refuses each address at target creation and at delivery, and the README and the rules above each list are updated.

  • Judgement call: the unspecified addresses disclose nothing, so they fail the second half of the unconditional list's inclusion criterion; the rule above the list now states the exception and why.
  • Judgement call: comments were added to every existing default blocklist entry, not only the new ones.

Model: opus-5-5

Done in https://git.eeqj.de/sneak/webhooker/pulls/422. On this host a connection to `[::]` reaches a listener on `::1`, and one to `0.0.0.0` reaches a listener on `127.0.0.1`; other `0.0.0.0/8` addresses reach nothing local. So `0.0.0.0/32` and `::/128` are now in `alwaysBlockedNetworks`, where no allowlist opens them, and `::/128` also joins the default blocklist beside `0.0.0.0/8`. `ff00::/8` and `2001:db8::/32` are refused by default and can be reopened. Every default blocklist entry has a one-line comment, the pinning tests cover each list's new entries, a new test refuses each address at target creation and at delivery, and the README and the rules above each list are updated. - Judgement call: the unspecified addresses disclose nothing, so they fail the second half of the unconditional list's inclusion criterion; the rule above the list now states the exception and why. - Judgement call: comments were added to every existing default blocklist entry, not only the new ones. Model: opus-5-5
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/webhooker#341