Commit 0babae2db0

0babae2db039da73b1ef184b0fd61da49b249a2e

parent: 7673081613

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-28 08:45 UTC

webhook: refuse shared (100.64.0.0/10) and multicast addresses

Ref #279

Layout: unified · split

internal/webhook/webhook.go +8 −4
@@ -48,9 +48,9 @@ func ValidateURL(raw string, allowLocal bool) error {
4848}
4949
5050// CheckAddrs refuses host when any of its resolved addresses is
51// loopback, private or link-local, unless allowLocal. A caller resolves
52// immediately before connecting and connects only to the addresses it
53// checked.
51// loopback, private, shared (100.64.0.0/10), link-local, multicast or
52// unspecified, unless allowLocal. A caller resolves immediately before
53// connecting and connects only to the addresses it checked.
5454func CheckAddrs(host string, ips []net.IP, allowLocal bool) error {
5555 if allowLocal {
5656 return nil
@@ -63,9 +63,13 @@ func CheckAddrs(host string, ips []net.IP, allowLocal bool) error {
6363 return nil
6464}
6565
66// cgnat is the shared address space of RFC 6598, which carriers and
67// overlay networks such as Tailscale use as private space.
68var cgnat = &net.IPNet{IP: net.IPv4(100, 64, 0, 0), Mask: net.CIDRMask(10, 32)}
69
6670func isForbidden(ip net.IP) bool {
6771 return ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() ||
68 ip.IsLinkLocalMulticast() || ip.IsUnspecified()
72 ip.IsMulticast() || ip.IsUnspecified() || cgnat.Contains(ip)
6973}
7074
7175type Deliverer struct {
internal/webhook/webhook_test.go +13
@@ -19,3 +19,16 @@ func TestCheckAddrs(t *testing.T) {
1919 t.Fatalf("allow_local: %v", err)
2020 }
2121}
22
23func TestIsForbiddenCGNATAndMulticast(t *testing.T) {
24 for _, s := range []string{"100.64.0.1", "100.127.255.254", "224.0.0.251", "239.1.2.3", "ff02::1", "ff0e::1"} {
25 if !isForbidden(net.ParseIP(s)) {
26 t.Errorf("%s allowed", s)
27 }
28 }
29 for _, s := range []string{"100.63.255.255", "100.128.0.1", "203.0.113.5", "2001:db8::1"} {
30 if isForbidden(net.ParseIP(s)) {
31 t.Errorf("%s refused", s)
32 }
33 }
34}