| CVE | Vulnerability name | Date | Responsible Security Disclosure by | Vulnerabilities |
|---|---|---|---|---|
|
GHSA-mwq8-ccpm-r533
|
ExcelBleed |
2026-07-05 |
sec-reex (coordinated disclosure) and xet7 (fix)
![]() Did send detailed report with runtime-confirmed PoC! |
|
WeKan boards are membership-scoped: a private board is only readable by its active members. The
REST export routes gate access with an async canExport(user) guard that resolves the
board and returns board.isVisibleBy(user). Every export route
(models/export.js, exportPDF.js, exportExcelCard.js,
import.js) awaits that guard — except the Excel-export route.
In models/exportExcel.js the guard was called without await.
Because canExport is async, it returns a Promise, and a Promise object
is always truthy — so the || short-circuit approved the export unconditionally and
the real visibility result (which never resolved before the branch was taken) was ignored:
// models/exportExcel.js (vulnerable)
const exporterExcel = new ExporterExcel(boardId, userLanguage);
if (exporterExcel.canExport(user) || impersonateDone) { // <-- missing await; Promise is always truthy
...
exporterExcel.build(res); // <-- runs for ANY authenticated user
} else {
res.end(TAPi18n.__('user-can-not-export-excel'));
}
// models/server/ExporterExcel.js
async canExport(user) {
const board = await ReactiveCache.getBoard(this._boardId);
return board && board.isVisibleBy(user); // correct — but never awaited above
}
Steps to reproduce (runtime-confirmed on v9.57.0). As non-member bob against a private board owned by alice:
GET /api/boards/<id>/export?authToken=<BOB> (JSON, correctly
awaited) → 403.GET /api/boards/<id>/exportExcel?authToken=<BOB> →
200 with a real .xlsx; xl/sharedStrings.xml contains alice's
private data.
Impact: an authenticated non-member could read the full contents of any board by id —
card titles and descriptions, lists, swimlanes, members and metadata — including private boards
they are not a member of. This is the same un-awaited async-auth bug class WeKan already
remediated in BFLABleed (48 REST endpoints, v9.22),
CloneBleed (un-awaited allowIsBoardMemberByCard, v9.35)
and TokenBleed. The Excel-export route was the lone remaining
un-awaited canExport call site.
The guard is now awaited, matching every other export route:
// models/exportExcel.js (fixed)
if ((await exporterExcel.canExport(user)) || impersonateDone) {
...
exporterExcel.build(res);
} else {
res.end(TAPi18n.__('user-can-not-export-excel'));
}
A non-member now correctly receives the "cannot export" response for
/api/boards/:boardId/exportExcel, identical to the already-hardened
/export route.
| Timeline | Details |
|---|---|
| 2026-07-05 |
Report received from sec-reex (coordinated disclosure, GHSA-mwq8-ccpm-r533), with a
read-only, runtime-confirmed PoC as part of an incomplete-patch measurement study. |
| Upcoming release | Fixed at the
upcoming WeKan release,
by adding await to the canExport guard in
models/exportExcel.js so the Excel-export route enforces board visibility like
every other export route.
|