refactor(storage): tighten S3 endpoint URL validation

Reuse the existing SafeWebhookUrl rule on the S3 Storage endpoint field so
the create and edit forms go through the same URL-normalization path as
webhook settings. Adds a matching guard inside S3Storage::testConnection()
so background callers (scheduled backups, database import reuse) also
validate the endpoint before building the S3 client.

Also fixes an IPv6-bracket edge case in SafeWebhookUrl so `http://[::1]`
style hosts are normalized before the filter_var IP check — the rule's
own loopback test was already asserting this behaviour.
This commit is contained in:
Andras Bacsai committed 2026-04-20 11:50:19 +02:00
1 parent 57ea0764b8
commit 297e9c41e1
5 files changed
+113 -6

No files matched your search

+8 -2
View File
@@ -40,9 +40,15 @@ class SafeWebhookUrl implements ValidationRule
$host = strtolower($host);
// Strip IPv6 brackets (e.g. "[::1]" -> "::1") before IP checks so bracketed
// literals can't sneak past filter_var FILTER_VALIDATE_IP.
$hostForIpCheck = (str_starts_with($host, '[') && str_ends_with($host, ']'))
? substr($host, 1, -1)
: $host;
// Block well-known dangerous hostnames
$blockedHosts = ['localhost', '0.0.0.0', '::1'];
if (in_array($host, $blockedHosts) || str_ends_with($host, '.internal')) {
if (in_array($hostForIpCheck, $blockedHosts) || str_ends_with($host, '.internal')) {
Log::warning('Webhook URL points to blocked host', [
'attribute' => $attribute,
'host' => $host,
@@ -55,7 +61,7 @@ class SafeWebhookUrl implements ValidationRule
}
// Block loopback (127.0.0.0/8) and link-local/metadata (169.254.0.0/16) when IP is provided directly
if (filter_var($host, FILTER_VALIDATE_IP) && ($this->isLoopback($host) || $this->isLinkLocal($host))) {
if (filter_var($hostForIpCheck, FILTER_VALIDATE_IP) && ($this->isLoopback($hostForIpCheck) || $this->isLinkLocal($hostForIpCheck))) {
Log::warning('Webhook URL points to blocked IP range', [
'attribute' => $attribute,
'host' => $host,