From 7c49986a5646e892a115725abb43cf97b5d7c1d2 Mon Sep 17 00:00:00 2001 From: Fuwn Date: Mon, 1 Jun 2026 15:11:27 +0000 Subject: fix(security): authorize shadowHide target in badges endpoint (IDOR) PUT /api/badges?shadowHide= called setShadowHidden on an arbitrary user_id with no ownership/privilege check, so any logged-in user could flip shadow_hidden on another user's badges (e.g. un-hide moderator-hidden ones). The GraphQL path already guarded this; the REST twin didn't. Extract the owner-or-privileged check into a shared isOwnerOrPrivileged helper, use it in both the REST endpoint and the GraphQL resolver, and add a regression test. --- src/routes/api/badges/+server.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'src/routes/api/badges') diff --git a/src/routes/api/badges/+server.ts b/src/routes/api/badges/+server.ts index 2673273c..10b63125 100644 --- a/src/routes/api/badges/+server.ts +++ b/src/routes/api/badges/+server.ts @@ -16,6 +16,7 @@ import { import { decodeAuthCookieOrNull } from "$lib/Effect/authCookie"; import { decodeRequestJsonOrThrow } from "$lib/Effect/requestBody"; import { appOrigin, appOriginHeaders } from "$lib/Utility/appOrigin"; +import { isOwnerOrPrivileged } from "$lib/Utility/authorisation"; import privilegedUser from "$lib/Utility/privilegedUser"; const unauthorised = () => new Response("Unauthorised", { status: 401 }); @@ -76,11 +77,14 @@ export const PUT = async ({ cookies, url, request }) => { if (!identity) return unauthorised(); const authorised = privilegedUser(identity.id); - if (url.searchParams.get("shadowHide")) - await setShadowHidden( - Number(url.searchParams.get("shadowHide")), - authorised, - ); + if (url.searchParams.get("shadowHide")) { + const targetUserId = Number(url.searchParams.get("shadowHide")); + + if (!isOwnerOrPrivileged(identity.id, targetUserId, authorised)) + return unauthorised(); + + await setShadowHidden(targetUserId, authorised); + } if (url.searchParams.get("import") || undefined) { const importedBadges = await decodeRequestJsonOrThrow( -- cgit v1.2.3