Skip to content

fix(notifications): sanitize webhook URLs before logging them - #987

Open
m4bard wants to merge 2 commits into
Listenarrs:canaryfrom
m4bard:fix/webhook-url-sanitize
Open

m4bard wants to merge 2 commits into
Listenarrs:canaryfrom
m4bard:fix/webhook-url-sanitize

Conversation

@m4bard

@m4bard m4bard commented Sep 14, 2026

Copy link
Copy Markdown

Fixes #982.

NotificationService.Webhooks.cs logged every outbound provider request at Information level using LogRedaction.RedactText(webhookUrl, LogRedaction.GetSensitiveValuesFromEnvironment()). That helper only replaces values sourced from the process environment, so a webhook URL typed into Settings was never touched and went out whole. The credential sits in the query string for Pushover and Pushbullet and in a path segment for Telegram, Discord and Slack.

The fix

Added LogRedaction.SanitizeWebhookUrl. It keeps scheme, host and port, drops userinfo and the whole query string (the same logic SanitizeUrl already uses), and for the providers whose credential lives in the path, masks just the segment(s) carrying it instead of dropping the whole path:

  • api.telegram.org/bot<token>/sendMessage becomes api.telegram.org/bot<redacted>/sendMessage
  • discord.com/api/webhooks/<id>/<token> becomes discord.com/api/webhooks/<redacted>/<redacted>
  • hooks.slack.com/services/<team>/<bot>/<token> becomes hooks.slack.com/services/<redacted>/<redacted>/<redacted>
  • every other host keeps its path and loses its query, same as SanitizeUrl

Wired it into all 25 {WebhookUrl} logging call sites in NotificationService.Webhooks.cs (NTFY, Pushover, Telegram, Pushbullet, Slack, Discord, generic), plus NotificationDiagnostics.LogFailedResponseAsync, replacing both the RedactText calls and the three call sites that were already using the query-only SanitizeUrl.

Also dropped the Pushover form body from its Information log line entirely instead of trying to redact it. That body carries token= and user= as plain form fields, which the existing AggressiveRedact helper cannot catch for the same environment-only reason. I checked the other providers' request bodies (NTFY, Telegram, Pushbullet, Slack, generic) and none of them put a credential in the body, so no change was needed there.

Tests

Added to tests/Features/Application/Security/Redaction/LogRedactionTests.cs:

  • One SanitizeWebhookUrl test per provider shape (Telegram, Discord, Slack, Pushover), each checking that the credential is gone and the non-sensitive host/path survives.
  • A control case with no credential-shaped content, so the tests cannot pass from a version that just blanks the whole URL.

Added to tests/Features/Application/Security/Redaction/SecurityRedactionTests.cs:

  • Three integration-level tests that run the real Pushover, Telegram and Discord code paths in NotificationService.SendNotificationAsync with a credential that is deliberately not an environment variable, capturing the actual formatted log line through a mocked ILogger. If any of those call sites regressed to the old RedactText(webhookUrl, GetSensitiveValuesFromEnvironment()) shape, the credential would appear in the captured log line and these would fail.

Ran the full backend suite: dotnet test tests/Listenarr.Tests.csproj, 3126 passed, 130 skipped, 0 failed, at commit ffdc5e2.

Sequencing

This touches NotificationService.Webhooks.cs, which #754 and #943 are also open against, per the issue's note. Happy to rebase after either lands.

Disclosure: drafted with Claude Code at my direction; I read the cited code at commit ffdc5e2 and reviewed this before posting.

m4bard and others added 2 commits September 13, 2026 19:41
NotificationService.Webhooks.cs logged every outbound provider request
at Information level with LogRedaction.RedactText(webhookUrl,
GetSensitiveValuesFromEnvironment()). RedactText only replaces values
sourced from the process environment, so a webhook URL typed into
Settings was never touched and went out whole. The credential lives in
the query string for Pushover and Pushbullet and in a path segment for
Telegram, Discord and Slack.

Add LogRedaction.SanitizeWebhookUrl: keeps scheme/host/port, drops
userinfo and the query string (same logic as the existing SanitizeUrl),
and for the four providers whose credential lives in the path, masks
just the segment(s) carrying it instead of dropping the whole path
(api.telegram.org/bot<redacted>/sendMessage,
discord.com/api/webhooks/<redacted>/<redacted>,
hooks.slack.com/services/<redacted>/...). Every other host keeps its
path and loses its query, matching SanitizeUrl's existing behavior.

Wire it into all 25 {WebhookUrl} logging call sites in
NotificationService.Webhooks.cs (NTFY, Pushover, Telegram, Pushbullet,
Slack, Discord, generic) and into
NotificationDiagnostics.LogFailedResponseAsync, replacing both the
RedactText call and the three call sites that already used SanitizeUrl.

Also drop the Pushover form body from its Information log line
entirely instead of trying to redact it: the body carries token= and
user= as plain form fields, which AggressiveRedact cannot catch for
the same environment-only reason. Checked the other providers' request
bodies (NTFY, Telegram, Pushbullet, Slack, generic) - none of them put
a credential in the body, so no change was needed there.

Fixes Listenarrs#982.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… call sites

Per-provider unit tests for LogRedaction.SanitizeWebhookUrl (Telegram, Discord,
Slack, Pushover, plus a control case with no credential-shaped content, so the
tests can't pass from a version that just blanks the whole URL).

Also three NotificationService integration tests that exercise the real
Pushover/Telegram/Discord code paths in NotificationService.Webhooks.cs with a
credential that is deliberately not an environment variable: if any of those
call sites regressed to LogRedaction.RedactText(webhookUrl,
GetSensitiveValuesFromEnvironment()), the credential would sail straight
through into the captured log line and these would fail.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@m4bard
m4bard requested a review from a team September 14, 2026 09:20
@m4bard
m4bard force-pushed the fix/webhook-url-sanitize branch from ffdc5e2 to 86ff24d Compare September 15, 2026 18:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pushover, Telegram and Pushbullet webhook URLs are logged with their credential intact

1 participant