From 662631a27948d431e6bd37fed15077b1bcccc7f8 Mon Sep 17 00:00:00 2001 From: Fuwn Date: Mon, 1 Jun 2026 15:37:21 +0000 Subject: fix(security): allow-list web-push endpoints to stop SSRF Stored push subscriptions carry a client-supplied `endpoint`, and the notifications job POSTed to it with no host check, so a subscription with an internal/metadata URL turned the Trigger.dev worker into a blind SSRF primitive. Add isAllowedPushEndpoint (https + known vendor hosts: FCM, Mozilla, Apple, WNS), skip non-conforming endpoints in the job, and reject them at subscribe time. Browser-minted subscriptions always match a vendor host, so real delivery is unchanged; a behaviour-gate test asserts vendor endpoints pass and internal/non-https/look-alikes fail. --- src/lib/Utility/pushEndpoint.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/lib/Utility/pushEndpoint.ts (limited to 'src/lib/Utility/pushEndpoint.ts') diff --git a/src/lib/Utility/pushEndpoint.ts b/src/lib/Utility/pushEndpoint.ts new file mode 100644 index 00000000..702164ae --- /dev/null +++ b/src/lib/Utility/pushEndpoint.ts @@ -0,0 +1,26 @@ +/** + * Web Push endpoints are minted by the browser's PushManager and always point at + * a known vendor push service — the user and the app never choose them. Limiting + * outbound delivery to these hosts stops the notifications job from being used as + * an SSRF primitive: a stored subscription with an arbitrary `endpoint` would + * otherwise have the worker POST to any URL, including internal/metadata hosts. + */ +const allowedPushHosts: RegExp[] = [ + /^fcm\.googleapis\.com$/, // Chrome, Edge, Brave, Opera, Samsung Internet + /(^|\.)push\.services\.mozilla\.com$/, // Firefox + /^web\.push\.apple\.com$/, // Safari / Apple + /(^|\.)notify\.windows\.com$/, // legacy Edge / Windows (WNS) +]; + +export const isAllowedPushEndpoint = (endpoint: string): boolean => { + try { + const url = new URL(endpoint); + + return ( + url.protocol === "https:" && + allowedPushHosts.some((host) => host.test(url.hostname)) + ); + } catch { + return false; + } +}; -- cgit v1.2.3