package handlers import ( "fmt" "net" "net/url" "strings" ) // validateCallbackURL ensures the URL points to a trusted OnlyOffice Document Server // and is not targeting internal/private network addresses. func validateCallbackURL(rawURL string, trustedHosts []string) error { parsed, err := url.Parse(rawURL) if err != nil { return fmt.Errorf("invalid URL: %w", err) } // Require HTTPS (or HTTP for Docker-internal communication) if parsed.Scheme != "https" && parsed.Scheme != "http" { return fmt.Errorf("unsupported scheme: %s", parsed.Scheme) } // Check against trusted hosts hostname := parsed.Hostname() trusted := false for _, h := range trustedHosts { if strings.EqualFold(hostname, h) { trusted = true break } } if !trusted { return fmt.Errorf("untrusted host: %s", hostname) } // Resolve DNS and block private/loopback IPs to prevent DNS rebinding ips, err := net.LookupIP(hostname) if err != nil { return fmt.Errorf("DNS resolution failed: %w", err) } for _, ip := range ips { if ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() { // Allow private IPs only if the trusted host is explicitly an IP if net.ParseIP(hostname) == nil { return fmt.Errorf("resolved to private IP: %s", ip) } } } return nil }