aboutsummaryrefslogtreecommitdiff
path: root/src/routes/api/notifications/subscribe
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-06-01 15:37:21 +0000
committerFuwn <[email protected]>2026-06-01 15:37:21 +0000
commit662631a27948d431e6bd37fed15077b1bcccc7f8 (patch)
treeb71ddebe246ffb51fb214664233f6a38a26a6211 /src/routes/api/notifications/subscribe
parentfix(security): authorize shadowHide target in badges endpoint (IDOR) (diff)
downloaddue.moe-662631a27948d431e6bd37fed15077b1bcccc7f8.tar.xz
due.moe-662631a27948d431e6bd37fed15077b1bcccc7f8.zip
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.
Diffstat (limited to 'src/routes/api/notifications/subscribe')
-rw-r--r--src/routes/api/notifications/subscribe/+server.ts17
1 files changed, 13 insertions, 4 deletions
diff --git a/src/routes/api/notifications/subscribe/+server.ts b/src/routes/api/notifications/subscribe/+server.ts
index 51dbf340..b1913e5d 100644
--- a/src/routes/api/notifications/subscribe/+server.ts
+++ b/src/routes/api/notifications/subscribe/+server.ts
@@ -3,6 +3,7 @@ import { safeUserIdentity } from "$lib/Data/AniList/identity";
import { setUserSubscription } from "$lib/Database/SB/User/notifications";
import { decodeAuthCookieOrNull } from "$lib/Effect/authCookie";
import { decodeRequestJsonOrThrow } from "$lib/Effect/requestBody";
+import { isAllowedPushEndpoint } from "$lib/Utility/pushEndpoint";
const unauthorised = new Response("Unauthorised", { status: 401 });
@@ -20,12 +21,20 @@ export const POST = async ({ cookies, request, url }) => {
if (!userId) return unauthorised;
+ const subscription = await decodeRequestJsonOrThrow(
+ request,
+ Schema.Record(Schema.String, Schema.Unknown),
+ );
+
+ if (
+ typeof subscription.endpoint !== "string" ||
+ !isAllowedPushEndpoint(subscription.endpoint)
+ )
+ return new Response("Invalid push endpoint", { status: 400 });
+
await setUserSubscription(
userId,
- (await decodeRequestJsonOrThrow(
- request,
- Schema.Record(Schema.String, Schema.Unknown),
- )) as unknown as JSON,
+ subscription as unknown as JSON,
fingerprint,
);