| CVE | Vulnerability name | Date | Responsible Security Disclosure by | Vulnerabilities |
|---|---|---|---|---|
|
GHSA-8cqr-x6m5-v4w6
|
PurgeBleed |
2026-08-15 |
ybsun0215 and xet7
![]() Coordinated disclosure via GitHub Security Advisory GHSA-8cqr-x6m5-v4w6. |
|
WeKan can delete a single card over the REST API:
DELETE /api/boards/:boardId/lists/:listId/cards/:cardId
Three identifiers, all supplied by the caller. The handler used the first to decide whether the request was allowed, and the third to decide what to act on:
// server/models/cards.js — before
await Authentication.checkBoardWriteAccess(req.userId, paramBoardId); // authorises the URL's board
const card = await ReactiveCache.getCard(paramCardId); // ANY board's card
if (card) {
await cardRemover(req.body.authorId, card); // destroys the children
}
await Cards.direct.removeAsync({ // triple key: no match
_id: paramCardId, listId: paramListId, boardId: paramBoardId,
});
cardRemover removes strictly by card id, with no board constraint at all:
await ChecklistItems.direct.removeAsync({ cardId: doc._id });
await Checklists.direct.removeAsync({ cardId: doc._id });
await CardComments.direct.removeAsync({ cardId: doc._id });
await Activities.direct.removeAsync({ cardId: doc._id });
await Cards.removeAsync({ parentId: doc._id }); // subcard cascade
So the order is what makes this severe. The children are destroyed against the card the bare lookup returned; the card itself is then removed against a selector that a foreign card can never match. The victim keeps an empty card and a 200, and the attacker keeps their access.
The fix constrains the lookup the way the bulk endpoint always did, so the card must be on the board the caller was authorised for:
// server/models/cards.js — after
const card = await ReactiveCache.getCard({
_id: paramCardId,
boardId: paramBoardId,
});
A card on another board is not found, cardRemover is not reached, and nothing is destroyed. tests/restApiIdorBatch.test.cjs pins the constrained lookup, that the bare one does not come back, and that the bulk endpoint still has its own.