| CVE | Vulnerability name | Date | Responsible Security Disclosure by | Vulnerabilities |
|---|---|---|---|---|
|
TokenBleed CVE requested
|
TokenBleed |
2026-06-10 |
Zion Boggan (coordinated disclosure) and Claude
![]() Did send detailed report with PoC! |
|
Authentication.checkUserId in server/authentication.js is an
async function. Both of its throws — the 401 when userId is
undefined, and the 403 when the user is not an admin — therefore become
rejected promises rather than synchronous exceptions.
The REST handler for POST /api/createtoken/:userId in
server/models/users.js called the check without await, inside a plain
synchronous try/catch:
WebApp.handlers.post('/api/createtoken/:userId', function(req, res) {
try {
Authentication.checkUserId(req.userId); // not awaited — rejection is detached
const id = req.params.userId;
const token = Accounts._generateStampedLoginToken();
Accounts._insertLoginToken(id, token); // mints a login token for :userId
sendJsonResult(res, { code: 200, data: { _id: id, authToken: token.token } });
} catch (error) {
sendJsonResult(res, { code: 200, data: error });
}
});
A synchronous try/catch cannot catch a rejected promise, so the failed check never
stopped execution. The handler went on to mint and return a usable login token for whatever user
ID was in the URL. An unauthenticated request (req.userId is undefined)
hits the 401 branch, but that throw is also async and detached, so the bypass worked with
no credentials at all.
Impact: POST /api/createtoken/<admin-user-id> returned a usable
authToken for that admin. An attacker could obtain a target admin's user ID from
GET /api/users (exposed by the same problem), then mint an admin token and take over
the instance. Precondition: REST API enabled (WITH_API=true), which is the default
in the Snap and Docker builds.
The same pattern affected every other handler that called an async
Authentication check without await from a synchronous try/catch.
In server/models/users.js and server/models/boards.js these were:
GET /api/users — user enumerationGET /api/users/:userId — read any user (with boards)PUT /api/users/:userId — edit user / takeOwnership / disable-enable loginPOST /api/users/ — create a user (handler was not even async)DELETE /api/users/:userId — delete any userPOST /api/deletetoken — destroy any user's login tokensGET /api/boards and GET /api/boards_count — board listing/countsDELETE /api/boards/:boardId — delete any boardGET /api/users/:userId/boards — list any user's boards (un-awaited checkAdminOrCondition)POST /api/boards/:boardId/copy — copy any board (un-awaited checkAdminOrCondition, originally outside the try)
POST /api/createtoken/:userId was the most serious because it hands out a working
session.
Every async Authentication check is now awaited so a failed check rejects
before any privileged code runs, and the two handlers that were not async
(POST /api/users/ and POST /api/createtoken/:userId) were made
async. The POST /api/boards/:boardId/copy check was also moved inside the
handler's try/catch so a denied caller gets a clean error response.
| Timeline | Details |
|---|---|
| 2026-06-10 |
Report received from Zion Boggan (coordinated disclosure, CVE requested). |
| Upcoming release | Fixed at Upcoming WeKan release, by awaiting every async authentication check across the Admin REST API. |