notify: the log names a notification endpoint, not its webhook URL (closes #288)
check / check (push) Canceled after 0s

With debug logging on, each delivery logged the full Slack, Mattermost
or ntfy URL, whose path or query lets anyone who has it post to the
channel or topic. The two debug lines now give the endpoint's name and
the title; sendSlack is told which of Slack or Mattermost it sends to.
A webhook URL that does not parse gave a startup error quoting the
whole URL; it now gives only the reason. Errors from a failed request
come from the transport, not from an HTTP client, and carry no part of
the path or query; a test now checks that too.

Model: opus-5-5
This commit was merged in pull request #294.
This commit is contained in:
2026-10-15 14:48:17 +02:00
parent b79726acc6
commit 5e2c456e85
5 changed files with 219 additions and 10 deletions
+22 -8
View File
@@ -56,6 +56,15 @@ func IsAllowedScheme(scheme string) bool {
func ValidateWebhookURL(raw string) (*url.URL, error) {
u, err := url.ParseRequestURI(raw)
if err != nil {
// A *url.Error quotes the whole URL, and this error is
// logged at startup. A webhook URL's path or query lets
// anyone post to the channel or topic, so keep only the
// reason.
var urlErr *url.Error
if errors.As(err, &urlErr) {
err = urlErr.Err
}
return nil, fmt.Errorf("invalid URL: %w", err)
}
@@ -277,7 +286,7 @@ func (svc *Service) dispatchSlack(
svc.dispatch(ctx, "slack", func(c context.Context) error {
return svc.sendSlack(
c, svc.slackWebhookURL, ErrSlackFailed,
c, svc.slackWebhookURL, "slack", ErrSlackFailed,
title, message, priority,
)
})
@@ -295,8 +304,8 @@ func (svc *Service) dispatchMattermost(
ctx, "mattermost",
func(c context.Context) error {
return svc.sendSlack(
c, svc.mattermostWebhookURL, ErrMattermostFailed,
title, message, priority,
c, svc.mattermostWebhookURL, "mattermost",
ErrMattermostFailed, title, message, priority,
)
},
)
@@ -307,9 +316,11 @@ func (svc *Service) sendNtfy(
topicURL *url.URL,
title, message, priority string,
) error {
// The topic URL is left out of the log: it lets anyone who has
// it read and post to the topic.
svc.log.Debug(
"sending ntfy notification",
"topic", topicURL.String(),
"endpoint", "ntfy",
"title", title,
)
@@ -372,12 +383,15 @@ type SlackAttachment struct {
}
// sendSlack posts to a Slack or Mattermost incoming webhook, which
// take the same payload. An HTTP error status is returned wrapped in
// failed, ErrSlackFailed or ErrMattermostFailed, so the error names
// the endpoint.
// take the same payload. endpoint, "slack" or "mattermost", names
// the webhook in the log in place of its URL, which lets anyone who
// has it post to the channel. An HTTP error status is returned
// wrapped in failed, ErrSlackFailed or ErrMattermostFailed, so the
// error names the endpoint.
func (svc *Service) sendSlack(
ctx context.Context,
webhookURL *url.URL,
endpoint string,
failed error,
title, message, priority string,
) error {
@@ -388,7 +402,7 @@ func (svc *Service) sendSlack(
svc.log.Debug(
"sending webhook notification",
"url", webhookURL.String(),
"endpoint", endpoint,
"title", title,
)