| CVE | Vulnerability name | Date | Responsible Security Disclosure by | Vulnerabilities |
|---|---|---|---|---|
|
GHSA-whxm-pxgj-7wqv
|
GuestBleed |
2026-08-15 |
ybsun0215 and xet7
![]() Coordinated disclosure via GitHub Security Advisory GHSA-whxm-pxgj-7wqv. |
|
A card can be created or updated with arrays of members and assignees:
POST /api/boards/{boardId}/lists/{listId}/cards { "members": [""] }
PUT /api/boards/{boardId}/lists/{listId}/cards/{id} { "assignees": [""] }
They were persisted as given:
// server/models/cards.js — before
const members = req.body.members !== undefined ? coerceRestArrayParam(req.body.members) : undefined;
...
members, // stored as-is
And the "my cards" feed answered by card membership alone:
// server/models/cards.js — before
const selector = { archived: false, $or: [{ members: userId }, { assignees: userId }] };
const cards = await ReactiveCache.getCards(selector, { sort: { dueAt: 1 } });
Neither is wrong on its own. Together they are a channel: a member of a private board writes an outsider's id onto a card, and the outsider's own feed then returns that card's title, its board, list and swimlane ids, its dates and its co-members — for as long as the id stays on the card.
The rule the merge endpoint already enforced now covers the array shapes as well:
// server/models/cards.js — after
async function assignableOnBoard(board, ids) {
const out = [];
for (const id of Array.isArray(ids) ? ids : []) {
if (canAssignCardMember(board, id)) out.push(id);
}
return out;
}
An id that may not be assigned is dropped rather than refused, so a bulk edit does not fail over one stale id and the caller sees what was kept. The listing is filtered by board visibility as well, because fixing only the write path would leave every card placed before the release still answering. tests/restApiIdorBatch.test.cjs pins both.