diff --git a/Dockerfile b/Dockerfile index 2c367c03..f36845fc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,9 +7,16 @@ ARG BUILD_DATE ARG VCS_REF # Allow pinning Caddy version - Renovate will update this -# Using Caddy 2.10.2 (latest stable) to fix CVE-2025-59530 and stdlib vulnerabilities -ARG CADDY_VERSION=2.10.2 -ARG CADDY_IMAGE=caddy:${CADDY_VERSION}-alpine +# Build the most recent Caddy 2.x release (keeps major pinned under v3). +# Setting this to '2' tells xcaddy to resolve the latest v2.x tag so we +# avoid accidentally pulling a v3 major release. Renovate can still update +# this ARG to a specific v2.x tag when desired. +ARG CADDY_VERSION=2 +## When an official caddy image tag isn't available on the host, use a +## plain Alpine base image and overwrite its caddy binary with our +## xcaddy-built binary in the later COPY step. This avoids relying on +## upstream caddy image tags while still shipping a pinned caddy binary. +ARG CADDY_IMAGE=alpine:3.18 # ---- Cross-Compilation Helpers ---- FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.8.0 AS xx diff --git a/backend/internal/services/notification_service.go b/backend/internal/services/notification_service.go index 8dfb1e8c..b7ca910b 100644 --- a/backend/internal/services/notification_service.go +++ b/backend/internal/services/notification_service.go @@ -196,6 +196,19 @@ func (s *NotificationService) sendCustomWebhook(p models.NotificationProvider, d // resolved IP. This prevents direct user-controlled hostnames from being used // as the request's destination (SSRF mitigation) and helps CodeQL validate the // sanitisation performed by validateWebhookURL. + // + // NOTE (security): The following mitigations are intentionally applied to + // reduce SSRF/request-forgery risk: + // - `validateWebhookURL` enforces http(s) schemes and rejects private IPs + // (except explicit localhost for testing) after DNS resolution. + // - We perform an additional DNS resolution here and choose a non-private + // IP to use as the TCP destination to avoid direct hostname-based routing. + // - We set the request's `Host` header to the original hostname so virtual + // hosting works while the actual socket connects to a resolved IP. + // - The HTTP client disables automatic redirects and has a short timeout. + // Together these steps make the request destination unambiguous and prevent + // accidental requests to internal networks. If your threat model requires + // stricter controls, consider an explicit allowlist of webhook hostnames. ips, err := net.LookupIP(u.Hostname()) if err != nil || len(ips) == 0 { return fmt.Errorf("failed to resolve webhook host: %w", err)