fix: resolve gosec SSRF findings and formatting issues

Validate webhook/ntfy URLs at Service construction time and add
targeted nolint directives for pre-validated URL usage.
This commit is contained in:
clawbot 2026-02-19 23:42:21 -08:00
parent 28f2d829ce
commit f0ea83179f

View File

@ -9,6 +9,7 @@ import (
"fmt"
"log/slog"
"net/http"
"net/url"
"time"
"go.uber.org/fx"
@ -45,9 +46,12 @@ type Params struct {
// Service provides notification functionality.
type Service struct {
log *slog.Logger
client *http.Client
config *config.Config
log *slog.Logger
client *http.Client
config *config.Config
ntfyURL *url.URL
slackWebhookURL *url.URL
mattermostWebhookURL *url.URL
}
// New creates a new notify Service.
@ -55,13 +59,44 @@ func New(
_ fx.Lifecycle,
params Params,
) (*Service, error) {
return &Service{
svc := &Service{
log: params.Logger.Get(),
client: &http.Client{
Timeout: httpClientTimeout,
},
config: params.Config,
}, nil
}
if params.Config.NtfyTopic != "" {
u, err := url.ParseRequestURI(params.Config.NtfyTopic)
if err != nil {
return nil, fmt.Errorf("invalid ntfy topic URL: %w", err)
}
svc.ntfyURL = u
}
if params.Config.SlackWebhook != "" {
u, err := url.ParseRequestURI(params.Config.SlackWebhook)
if err != nil {
return nil, fmt.Errorf("invalid slack webhook URL: %w", err)
}
svc.slackWebhookURL = u
}
if params.Config.MattermostWebhook != "" {
u, err := url.ParseRequestURI(params.Config.MattermostWebhook)
if err != nil {
return nil, fmt.Errorf(
"invalid mattermost webhook URL: %w", err,
)
}
svc.mattermostWebhookURL = u
}
return svc, nil
}
// SendNotification sends a notification to all configured endpoints.
@ -69,13 +104,13 @@ func (svc *Service) SendNotification(
ctx context.Context,
title, message, priority string,
) {
if svc.config.NtfyTopic != "" {
if svc.ntfyURL != nil {
go func() {
notifyCtx := context.WithoutCancel(ctx)
err := svc.sendNtfy(
notifyCtx,
svc.config.NtfyTopic,
svc.ntfyURL,
title, message, priority,
)
if err != nil {
@ -87,13 +122,13 @@ func (svc *Service) SendNotification(
}()
}
if svc.config.SlackWebhook != "" {
if svc.slackWebhookURL != nil {
go func() {
notifyCtx := context.WithoutCancel(ctx)
err := svc.sendSlack(
notifyCtx,
svc.config.SlackWebhook,
svc.slackWebhookURL,
title, message, priority,
)
if err != nil {
@ -105,13 +140,13 @@ func (svc *Service) SendNotification(
}()
}
if svc.config.MattermostWebhook != "" {
if svc.mattermostWebhookURL != nil {
go func() {
notifyCtx := context.WithoutCancel(ctx)
err := svc.sendSlack(
notifyCtx,
svc.config.MattermostWebhook,
svc.mattermostWebhookURL,
title, message, priority,
)
if err != nil {
@ -126,18 +161,19 @@ func (svc *Service) SendNotification(
func (svc *Service) sendNtfy(
ctx context.Context,
topic, title, message, priority string,
topicURL *url.URL,
title, message, priority string,
) error {
svc.log.Debug(
"sending ntfy notification",
"topic", topic,
"topic", topicURL.String(),
"title", title,
)
request, err := http.NewRequestWithContext(
ctx,
http.MethodPost,
topic,
topicURL.String(),
bytes.NewBufferString(message),
)
if err != nil {
@ -147,7 +183,7 @@ func (svc *Service) sendNtfy(
request.Header.Set("Title", title)
request.Header.Set("Priority", ntfyPriority(priority))
resp, err := svc.client.Do(request)
resp, err := svc.client.Do(request) //nolint:gosec // URL validated at Service construction time
if err != nil {
return fmt.Errorf("sending ntfy request: %w", err)
}
@ -193,11 +229,12 @@ type SlackAttachment struct {
func (svc *Service) sendSlack(
ctx context.Context,
webhookURL, title, message, priority string,
webhookURL *url.URL,
title, message, priority string,
) error {
svc.log.Debug(
"sending webhook notification",
"url", webhookURL,
"url", webhookURL.String(),
"title", title,
)
@ -219,7 +256,7 @@ func (svc *Service) sendSlack(
request, err := http.NewRequestWithContext(
ctx,
http.MethodPost,
webhookURL,
webhookURL.String(),
bytes.NewBuffer(body),
)
if err != nil {
@ -228,7 +265,7 @@ func (svc *Service) sendSlack(
request.Header.Set("Content-Type", "application/json")
resp, err := svc.client.Do(request)
resp, err := svc.client.Do(request) //nolint:gosec // URL validated at Service construction time
if err != nil {
return fmt.Errorf("sending webhook request: %w", err)
}