mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-13 04:06:59 +00:00
fix(notifications): harden alert dispatch crash-safety and redact webhook secrets in logs (#1255)
* fix(notifications): harden alert dispatch crash-safety and redact webhook secrets in logs Make NotificationService.dispatchAlert never reject so the many fire-and-forget callers (monitors, event streams, policy and image-update paths) cannot trigger an unhandledRejection on an unhealthy database. The whole dispatch body now sits inside a guard covering node resolution, the history insert, channel-table reads, and the WebSocket broadcast; a failure logs and drops the notification instead of crashing the process. An inner guard still splits the write-success and write-failure metrics. Also: - Redact webhook URLs in diagnostic logs via a new maskWebhookUrl helper; Discord/Slack/custom webhook URLs embed their token in the path, so only the origin is safe to emit. - Add error logging to four notification-history route handlers that previously swallowed database errors silently before returning 500. - Snapshot the subscriber set before broadcasting so a close/error handler firing mid-send cannot mutate the set during iteration. - Sanitize the admin-supplied route name in dispatch log lines. Adds tests for dispatch crash-safety (write failure, post-write routing failure, broadcast send failure), the success-path write metric, webhook URL masking, and history-route error logging. * fix(notifications): sanitize route name and patterns in create logs Apply sanitizeForLog to the admin-supplied route name and stack patterns in the route-creation log lines, matching the dispatch-site sanitization and closing the remaining log-injection path on this feature. Also extend tests: userinfo-stripping in maskWebhookUrl, subscriber-set snapshot behavior under an unsubscribe-during-send, and error logging on the mark-read, delete-one, and clear-all notification-history handlers.
This commit is contained in:
@@ -9,3 +9,22 @@ export function validateHttpsUrl(value: unknown): string | null {
|
||||
try { new URL(value); } catch { return 'is not a valid URL'; }
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mask a channel webhook URL for logging. Discord/Slack/custom webhook URLs
|
||||
* embed their auth token in the path (and sometimes the query), so only the
|
||||
* origin is safe to emit. Returns `https://host/<redacted>` or a generic
|
||||
* placeholder if the value is not a parseable URL.
|
||||
*/
|
||||
export function maskWebhookUrl(value: unknown): string {
|
||||
if (typeof value !== 'string' || value.length === 0) return '<no url>';
|
||||
try {
|
||||
const { origin, pathname, search } = new URL(value);
|
||||
// pathname is normalized to '/' for an origin-only URL, so anything else
|
||||
// (or any query string) is a token-bearing segment we must not log.
|
||||
const hasSecret = pathname !== '/' || search !== '';
|
||||
return hasSecret ? `${origin}/<redacted>` : origin;
|
||||
} catch {
|
||||
return '<invalid url>';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user