From 198509e7600981400353aec6259247b3c04e043e Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Mon, 29 Dec 2025 16:39:23 +0200 Subject: [PATCH] Security Fix 4: Cross-board card move without destination authorization. Thanks to Joshua Rogers of joshua.hu, Twitter MegaManSec ! --- models/cards.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/models/cards.js b/models/cards.js index 7aa9bf4d0..7bb486e6b 100644 --- a/models/cards.js +++ b/models/cards.js @@ -4292,6 +4292,37 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', function( ); } if (newBoardId && newSwimlaneId && newListId) { + // Validate destination board access + Authentication.checkBoardAccess(req.userId, newBoardId); + + // Validate that the destination list exists and belongs to the destination board + const destList = ReactiveCache.getList({ + _id: newListId, + boardId: newBoardId, + archived: false, + }); + if (!destList) { + JsonRoutes.sendResult(res, { + code: 404, + data: { error: 'Destination list not found or does not belong to destination board' }, + }); + return; + } + + // Validate that the destination swimlane exists and belongs to the destination board + const destSwimlane = ReactiveCache.getSwimlane({ + _id: newSwimlaneId, + boardId: newBoardId, + archived: false, + }); + if (!destSwimlane) { + JsonRoutes.sendResult(res, { + code: 404, + data: { error: 'Destination swimlane not found or does not belong to destination board' }, + }); + return; + } + // Move the card to the new board, swimlane, and list Cards.direct.update( {