diff --git a/app/Rules/SafeWebhookUrl.php b/app/Rules/SafeWebhookUrl.php index 0316da0bc..415f94df7 100644 --- a/app/Rules/SafeWebhookUrl.php +++ b/app/Rules/SafeWebhookUrl.php @@ -134,10 +134,17 @@ class SafeWebhookUrl implements ValidationRule return []; } - return array_map( - fn (string $ip): string => sprintf('%s:%d=%s', $target['host'], $target['port'], filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? '['.$ip.']' : $ip), + // The MinIO client keeps one --resolve IP per host and parses it with Go's + // netip.ParseAddr, which rejects bracketed IPv6. Emit a single entry, preferring + // IPv4 for reachability, without brackets. + $preferredIps = array_values(array_filter( $target['ips'], - ); + fn (string $ip): bool => filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false, + )); + + $ip = $preferredIps[0] ?? $target['ips'][0]; + + return [sprintf('%s:%d=%s', $target['host'], $target['port'], $ip)]; } public static function redactedUrlForLog(string $url): string diff --git a/tests/Unit/SafeWebhookUrlTest.php b/tests/Unit/SafeWebhookUrlTest.php index 84d1b33ff..d02b0057d 100644 --- a/tests/Unit/SafeWebhookUrlTest.php +++ b/tests/Unit/SafeWebhookUrlTest.php @@ -239,6 +239,19 @@ it('builds MinIO client resolve options for S3 backup uploads', function () { expect($options)->toContain('localhost:9000=127.0.0.1'); }); +it('collapses dual-stack S3 hosts to a single bracket-free IPv4 resolve entry', function () { + InstanceSettings::unguarded(fn () => InstanceSettings::query()->updateOrCreate(['id' => 0], [ + 'webhook_allowed_internal_hosts' => ['localhost'], + 'webhook_allow_localhost' => true, + ])); + + // localhost resolves to both 127.0.0.1 and ::1. The MinIO client keeps one IP per + // host and rejects bracketed IPv6, so exactly one bracket-free IPv4 entry is produced. + $options = SafeWebhookUrl::minioClientResolveOptions('http://localhost:9000'); + + expect($options)->toBe(['localhost:9000=127.0.0.1']); +}); + it('rejects trailing-dot hostnames to avoid DNS pinning mismatch', function () { $rule = new SafeWebhookUrl(fn (string $host): array => ['93.184.216.34']);