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 {
48} 48}
49 49
50// CheckAddrs refuses host when any of its resolved addresses is 50// CheckAddrs refuses host when any of its resolved addresses is
51// loopback, private or link-local, unless allowLocal. A caller resolves 51// loopback, private, shared (100.64.0.0/10), link-local, multicast or
52// immediately before connecting and connects only to the addresses it 52// unspecified, unless allowLocal. A caller resolves immediately before
53// checked. 53// connecting and connects only to the addresses it checked.
54func CheckAddrs(host string, ips []net.IP, allowLocal bool) error { 54func CheckAddrs(host string, ips []net.IP, allowLocal bool) error {
55 if allowLocal { 55 if allowLocal {
56 return nil 56 return nil
@@ -63,9 +63,13 @@ func CheckAddrs(host string, ips []net.IP, allowLocal bool) error {
63 return nil 63 return nil
64} 64}
65 65
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
66func isForbidden(ip net.IP) bool { 70func isForbidden(ip net.IP) bool {
67 return ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || 71 return ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() ||
68 ip.IsLinkLocalMulticast() || ip.IsUnspecified() 72 ip.IsMulticast() || ip.IsUnspecified() || cgnat.Contains(ip)
69} 73}
70 74
71type Deliverer struct { 75type Deliverer struct {
internal/webhook/webhook_test.go +13
@@ -19,3 +19,16 @@ func TestCheckAddrs(t *testing.T) {
19 t.Fatalf("allow_local: %v", err) 19 t.Fatalf("allow_local: %v", err)
20 } 20 }
21} 21}
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}