diff --git a/backend/internal/security/url_validator.go b/backend/internal/security/url_validator.go index fa368925..0d8d3c62 100644 --- a/backend/internal/security/url_validator.go +++ b/backend/internal/security/url_validator.go @@ -294,7 +294,14 @@ func ValidateExternalURL(rawURL string, options ...ValidationOption) (string, er continue } if network.IsPrivateIP(ipv4) { - return "", fmt.Errorf("connection to private ip addresses is blocked for security (detected IPv4-mapped IPv6: %s)", ip.String()) + // Normalize to the extracted IPv4 for both the cloud-metadata special-case + // and sanitization, so ::ffff:169.254.169.254 produces the same error as + // 169.254.169.254 and doesn't leak the raw IPv6 form in messages. + sanitizedIPv4 := sanitizeIPForError(ipv4.String()) + if ipv4.String() == "169.254.169.254" { + return "", fmt.Errorf("access to cloud metadata endpoints is blocked for security (detected: %s)", sanitizedIPv4) + } + return "", fmt.Errorf("connection to private ip addresses is blocked for security (detected IPv4-mapped IPv6: %s)", sanitizedIPv4) } } diff --git a/backend/internal/security/url_validator_test.go b/backend/internal/security/url_validator_test.go index 240c4c50..6fe99a11 100644 --- a/backend/internal/security/url_validator_test.go +++ b/backend/internal/security/url_validator_test.go @@ -1175,7 +1175,8 @@ func TestValidateExternalURL_WithAllowRFC1918_IPv4MappedIPv6Allowed(t *testing.T func TestValidateExternalURL_WithAllowRFC1918_IPv4MappedMetadataBlocked(t *testing.T) { t.Parallel() - // ::ffff:169.254.169.254 maps to the cloud metadata IP; must stay blocked. + // ::ffff:169.254.169.254 maps to the cloud metadata IP; must stay blocked and + // produce the same cloud-metadata error as the non-mapped address. _, err := ValidateExternalURL( "http://[::ffff:169.254.169.254]", WithAllowHTTP(), @@ -1185,4 +1186,10 @@ func TestValidateExternalURL_WithAllowRFC1918_IPv4MappedMetadataBlocked(t *testi if err == nil { t.Fatal("expected IPv4-mapped metadata address to be blocked, got nil") } + if !strings.Contains(err.Error(), "cloud metadata") { + t.Errorf("expected cloud-metadata error for ::ffff:169.254.169.254, got: %v", err) + } + if strings.Contains(err.Error(), "ffff") { + t.Errorf("error message must not leak the raw IPv6 form, got: %v", err) + } }