From a19718930610f304bf8d5a44a17c87c55bde8db7 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 1 Aug 2026 11:16:30 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=92=20Fix=20TOCTOU=20vulnerability=20i?= =?UTF-8?q?n=20post=20deletion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🎯 What: Addressed a Time-Of-Check to Time-Of-Use (TOCTOU) vulnerability in the post deletion endpoint (`src/routes/api/post/[id]/+server.ts`). ⚠️ Risk: Previously, the existence and ownership check was performed separately from the actual deletion operation, exposing a minor race condition that could have been exploited. 🛡️ Solution: Consolidated the check and deletion queries into a single atomic `.delete().eq().eq().select()` query to ensure they happen in one database transaction operation. Tests in `src/routes/api/post/[id]/server.test.ts` were updated to match the new atomic query structure. Co-authored-by: Sparkier <5690524+Sparkier@users.noreply.github.com> --- src/routes/api/post/[id]/+server.ts | 16 ++++++++-------- src/routes/api/post/[id]/server.test.ts | 7 ++++--- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/routes/api/post/[id]/+server.ts b/src/routes/api/post/[id]/+server.ts index a1efb79..6e7f6ec 100644 --- a/src/routes/api/post/[id]/+server.ts +++ b/src/routes/api/post/[id]/+server.ts @@ -7,21 +7,21 @@ export const DELETE = async ({ locals: { supabase, safeGetSession }, params }) = throw error(401, 'Unauthorized'); } - const { data: posts } = await supabase + const { error: deleteError, data } = await supabase .from('post') - .select() + .delete() .eq('id', params.id) - .eq('creator', session.user.email); - if (!posts?.length) { - throw error(404, 'Not found or unauthorized'); - } - - const { error: deleteError } = await supabase.from('post').delete().eq('id', params.id); + .eq('creator', session.user.email) + .select(); if (deleteError) { throw error(500, 'Internal Server Error'); } + if (!data?.length) { + throw error(404, 'Not found or unauthorized'); + } + return text('Post deleted'); }; diff --git a/src/routes/api/post/[id]/server.test.ts b/src/routes/api/post/[id]/server.test.ts index 026ae1b..699ada4 100644 --- a/src/routes/api/post/[id]/server.test.ts +++ b/src/routes/api/post/[id]/server.test.ts @@ -74,10 +74,11 @@ describe('PATCH /api/post/[id]', () => { describe('DELETE /api/post/[id]', () => { it('should throw 404 when deleting a non-existent post', async () => { // Create a mock chain for supabase - const mockEq2 = vi.fn().mockResolvedValue({ data: [] }); + const mockSelect = vi.fn().mockResolvedValue({ data: [], error: null }); + const mockEq2 = vi.fn().mockReturnValue({ select: mockSelect }); const mockEq1 = vi.fn().mockReturnValue({ eq: mockEq2 }); - const mockSelect = vi.fn().mockReturnValue({ eq: mockEq1 }); - const mockFrom = vi.fn().mockReturnValue({ select: mockSelect }); + const mockDelete = vi.fn().mockReturnValue({ eq: mockEq1 }); + const mockFrom = vi.fn().mockReturnValue({ delete: mockDelete }); const mockSupabase = { from: mockFrom