ValidateWebhookImageURL — see ValidateWebhookURL. ValidateWebhookURL checks that a webhook URL is safe to call (SSRF protection).
(rawURL string)
| 97 | // |
| 98 | // ValidateWebhookURL checks that a webhook URL is safe to call (SSRF protection). |
| 99 | func ValidateWebhookURL(rawURL string) error { |
| 100 | u, err := url.Parse(rawURL) |
| 101 | if err != nil { |
| 102 | return fmt.Errorf("invalid URL: %w", err) |
| 103 | } |
| 104 | |
| 105 | if u.Scheme != "https" { |
| 106 | return fmt.Errorf("webhook URL must use HTTPS") |
| 107 | } |
| 108 | |
| 109 | host := u.Hostname() |
| 110 | if host == "" { |
| 111 | return fmt.Errorf("webhook URL must have a host") |
| 112 | } |
| 113 | |
| 114 | // Reject IP addresses directly — require domain names |
| 115 | if ip := net.ParseIP(host); ip != nil { |
| 116 | return fmt.Errorf("webhook URL must use a domain name, not an IP address") |
| 117 | } |
| 118 | |
| 119 | // Resolve and reject private/loopback IPs |
| 120 | ips, err := net.LookupIP(host) |
| 121 | if err != nil { |
| 122 | return fmt.Errorf("cannot resolve webhook host %q: %w", host, err) |
| 123 | } |
| 124 | for _, ip := range ips { |
| 125 | if webhook.IsDisallowedWebhookIP(ip) { |
| 126 | return fmt.Errorf("webhook URL must not resolve to a private/loopback/link-local address") |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | return nil |
| 131 | } |
| 132 | |
| 133 | var ( |
| 134 | slugPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-]{0,38}[a-z0-9]$`) |
no test coverage detected