From 953e6aeca4a1cf5dcba2148ab638a357cd6e60a0 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Tue, 30 Dec 2025 22:12:04 +0900 Subject: fix(sync): verify card ownership before update in push MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, when updating an existing card during sync push, only the target deck ownership was verified. This allowed a user who knew another user's card ID to potentially update that card by specifying their own deck. Now the query joins with decks table to verify the existing card belongs to the current user. 🤖 Generated with [Claude Code](https://claude.ai/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/server/repositories/sync.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src/server') diff --git a/src/server/repositories/sync.ts b/src/server/repositories/sync.ts index a1b6648..3e9f8ed 100644 --- a/src/server/repositories/sync.ts +++ b/src/server/repositories/sync.ts @@ -171,18 +171,18 @@ export const syncRepository: SyncRepository = { for (const cardData of data.cards) { const clientUpdatedAt = new Date(cardData.updatedAt); - // Verify deck belongs to user + // Verify target deck belongs to user const deckCheck = await db .select({ id: decks.id }) .from(decks) .where(and(eq(decks.id, cardData.deckId), eq(decks.userId, userId))); if (deckCheck.length === 0) { - // Deck doesn't belong to user, skip + // Target deck doesn't belong to user, skip continue; } - // Check if card exists + // Check if card exists AND belongs to user (via deck ownership) const existing = await db .select({ id: cards.id, @@ -190,7 +190,8 @@ export const syncRepository: SyncRepository = { syncVersion: cards.syncVersion, }) .from(cards) - .where(eq(cards.id, cardData.id)); + .innerJoin(decks, eq(cards.deckId, decks.id)) + .where(and(eq(cards.id, cardData.id), eq(decks.userId, userId))); if (existing.length === 0) { // New card - insert -- cgit v1.2.3-70-g09d2