mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 13:21:46 +00:00
fix(security): address notification system security audit findings
- SSRF: guard sendWebhook() with checkSsrf() + createPinnedAgent() to block
requests to loopback, link-local, private network, and cloud metadata endpoints
- XSS: escape subject, body, and ctaHref in buildEmailHtml() via escapeHtml()
to prevent HTML injection through user-controlled params (actor, preview, etc.)
- Encrypt webhook URLs at rest: apply maybe_encrypt_api_key on save
(settingsService for user URLs, authService for admin URL) and decrypt_api_key
on read in getUserWebhookUrl() / getAdminWebhookUrl()
- Log failed channel dispatches: inspect Promise.allSettled() results and log
rejections via logError instead of silently dropping them
- Log admin webhook failures: replace fire-and-forget .catch(() => {}) with
.catch(err => logError(...)) and await the call
- Migration 69: guard against missing notification_preferences table on fresh installs
- Migration 70: drop the now-unused notification_preferences table
- Refactor: extract applyUserChannelPrefs() helper to deduplicate
setPreferences / setAdminPreferences logic
- Tests: add SEC-016 (XSS, 5 cases) and SEC-017 (SSRF, 6 cases) test suites;
mock ssrfGuard in notificationService tests
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { db } from '../db/database';
|
||||
import { logDebug } from './auditLog';
|
||||
import { logDebug, logError } from './auditLog';
|
||||
import {
|
||||
getActiveChannels,
|
||||
isEnabledForEvent,
|
||||
@@ -264,7 +264,12 @@ export async function send(payload: NotificationPayload): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.allSettled(promises);
|
||||
const results = await Promise.allSettled(promises);
|
||||
for (const result of results) {
|
||||
if (result.status === 'rejected') {
|
||||
logError(`notificationService.send channel dispatch failed event=${event} recipient=${recipientId}: ${result.reason}`);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// ── Admin webhook (scope: admin) — global, respects global pref ──────
|
||||
@@ -272,7 +277,9 @@ export async function send(payload: NotificationPayload): Promise<void> {
|
||||
const adminWebhookUrl = getAdminWebhookUrl();
|
||||
if (adminWebhookUrl) {
|
||||
const { title, body } = getEventText('en', event, params);
|
||||
sendWebhook(adminWebhookUrl, { event, title, body, link: fullLink }).catch(() => {});
|
||||
await sendWebhook(adminWebhookUrl, { event, title, body, link: fullLink }).catch((err: unknown) => {
|
||||
logError(`notificationService.send admin webhook failed event=${event}: ${err instanceof Error ? err.message : err}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user