This release includes 1 security fix for security teams reviewing exposed deployments.
Topics
+2 more
Affected surfaces
Summary
AI summaryBroad release touches https://wekan.fi/hall-of-fame/tokenbleed/, https://github.com/wekan/wekan/commit/08ae61161cd9602f79f441e3922ffe890b3f11de, https://github.com/wekan/wekan/commit/f1c2f1f40f21b9f467cfe7265d22bc98fb401b57, and https://github.com/wekan/wekan/commit/26a03f4845b9c81902f3f955d79287a4bc61d3c4.
Full changelog
v9.36 2026-06-10 WeKan ® release
This release fixes the following CRITICAL SECURITY ISSUES of TokenBleed:
- Fixed TokenBleed: unauthenticated login-token minting via un-awaited auth check in
POST /api/createtoken/:userId
(CWE-863, CWE-287).Authentication.checkUserIdinserver/authentication.jsis an
asyncfunction, so its 401 (undefineduserId) and 403 (not an admin) throws become
rejected promises rather than synchronous exceptions. The REST handlers in
server/models/users.jsandserver/models/boards.jscalled it withoutawaitinside a
plain synchronoustry/catch, which cannot catch a rejected promise, so the failed check
never stopped execution.POST /api/createtoken/:userIdthen went on to mint and return a
usable login token for any user ID in the URL — including an admin — with no credentials at
all (unauthenticated account takeover). The same detached-rejection bypass also affected
GET /api/users,GET /api/users/:userId,PUT /api/users/:userId,POST /api/users/,
DELETE /api/users/:userId,POST /api/deletetoken,GET /api/boards,
GET /api/boards_count,DELETE /api/boards/:boardId,GET /api/users/:userId/boardsand
POST /api/boards/:boardId/copy. Fixed by awaiting every asyncAuthenticationcheck (and
making the two non-asynchandlersasync) so a failed check rejects before any privileged
code runs. The same un-awaited pattern in the board/card/Excel/PDF export handlers
(models/export.js,models/exportExcel.js,models/exportExcelCard.js,models/exportPDF.js,
which were backstopped byexporter.canExport()) and in the checklist-create handler
(server/models/checklists.js) was given the same await pass. Affected Wekan v9.35 and earlier.
Thanks to Zion Boggan, xet7 and Claude.
and adds the following updates:
- Improved Security Advisory process.
Thanks to xet7. - Improved Contributing.
Thanks to xet7. - Improved Code of Conduct.
Thanks to xet7. - Update release scripts, trying to fix GitHub Actions builds.
Part 1,
Part 2.
Thanks to xet7 and Claude. - Updated dependencies.
Thanks to developers of dependencies.
and fixes the following bugs:
- Fix Admin Panel / Reports icons and spacing.
Thanks to xet7. - Fix MultiSelect, so that it's possible to click select checkbox or minicard to select.
Thanks to xet7 and Claude. - Fix LDAP_SYNC_ADMIN_GROUPS.
so that admin status sync and group/role sync no longer require
LDAP_GROUP_FILTER_ENABLE=true, which only controls the login restriction filter
Thanks to xet7 and Claude. - Expand and fix REST API
(Admin API, board member, card field, card copy/move).
REST routes live inserver/models/*.js(usingWebApp.handlers.get/post/put/delete),
their schemas inmodels/*.js, OpenAPI docs are generated byopenapi/generate_openapi.py
intopublic/api/wekan.yml, andapi.pyis the Python CLI wrapper.
api.pyalready gained CLI wrappers for the endpoints that exist today:
addboardmember,removeboardmember,setboardmemberrole,setcardmembers,
setcardassignees,setcarddate,setcardlabels,movecard. - Issue #5998: Add/remove user to
board with a role (BoardAdmin, Normal, etc).
Already exists server-side:POST /api/boards/:boardId/members/:userId/add
andPOST /api/boards/:boardId/members/:userId/remove(action=add/remove), and
POST /api/boards/:boardId/members/:memberIdto change an existing member's
permission flags (isAdmin,isCommentOnly,isWorker,isNoComments,
isNormalAssignedOnly,isCommentAssignedOnly,isReadOnly,
isReadAssignedOnly).api.pynow wraps these with a friendlyROLE
name → flags mapping.
Implemented: the API itself now also accepts a single namedrole
(admin/normal/commentOnly/worker/readOnly/normalAssignedOnly/
commentAssignedOnly/readAssignedOnly/noComments), mapped to the permission
booleans server-side byboardMemberRoleToFlags(server/lib/utils.js). When
roleis present it wins over the individual flags; an unknown role returns 400.
Members are identified byuserIdonly. Wired into both
POST .../members/:userId/addandPOST .../members/:memberId.
Thanks to xet7 and Claude. - Add/Remove board member to card as card member or card assignee.
Already exists viaPUT /api/boards/:boardId/lists/:listId/cards/:cardIdwith
members/assignees(array, or '' to clear).api.pynow wraps these as
setcardmembers/setcardassignees. These REPLACE the list.
Implemented: merge-style endpoints to add/remove a single member or assignee
(POST/DELETE /api/boards/:boardId/lists/:listId/cards/:cardId/members/:memberId
and.../assignees/:assigneeId), using$addToSet/$pullso callers don't
read-modify-write the whole array. Adding validates board membership
(canAssignCardMember) and rejects non-members with 400; removing a stale id is
allowed. Wrapped inapi.pyasaddcardmember/removecardmember/
addcardassignee/removecardassignee.
Thanks to xet7 and Claude. - Issue #5897: Create Linked Card.
Linked cards reference another card; the data model useslinkedId/
type: 'cardType-linkedCard'. Implemented by reusing the existing
POST /api/boards/:boardId/lists/:listId/cardsroute: whenlinkedIdis present
in the body the new card is created viaCard.link(type cardType-linkedCard)
instead of holding its own content. Linking across boards is allowed; the caller
must have write access to the destination board and read access to the linked
card's board (checkBoardAccess). Wrapped inapi.pyaslinkcard.
Thanks to xet7 and Claude. - Fix Issue #5846: Add/Remove card
dates (Received/Start/Due/End). Setting dates already worked viaPUTcard with
receivedAt,startAt,dueAt,endAt, but the handler only wrote a date when
the value was truthy (if (req.body.receivedAt)), so an empty string could NOT
clear a date — removing a date via the API did not work. Fixed in
server/models/cards.js: whenever a date field is present in the request body, an
empty string /null/"null"now$unsets the date and any other value sets
it.api.pywraps this assetcarddate ... DATETYPE [DATEVALUE](omit DATEVALUE,
or pass an empty string, to clear).
Thanks to xet7 and Claude. - Issue #5819: Bulk add/remove labels
with API, and BoardAdmin label management in the right sidebar Labels
hamburger/trigram menu.
PUTcardlabelIdsREPLACES the whole label set (wrapped assetcardlabels).
Implemented: a multi-card bulk endpointPOST /api/boards/:boardId/cards/labels
taking{cardIds, addLabelIds, removeLabelIds}that MERGES — existing labels are
kept,removeLabelIdsare dropped,addLabelIdsare added, de-duplicated, across
all listed cards in one request.addLabelIdsare validated against the board's
labels (request rejected with the offending ids otherwise); cards not on the board
are reported innotFound. Wrapped inapi.pyasbulkcardlabels.
Board label creation viaPUT /api/boards/:boardId/labelsis now gated to
BoardAdmin (allowIsBoardAdmin); normal members can still apply existing labels
to cards (including via the bulk endpoint). Still planned: the right-sidebar
Labels hamburger/trigram menu BoardAdmin-only UI. Thanks to xet7 and Claude. - Fix Issue #5813: Card number is not
unique when concurrently creating multiple cards via REST API; many cards got the
same card number. Root cause:Board.getNextCardNumber()(models/boards.js)
read the current maxcardNumberand returnedmax + 1; two concurrent card
creations read the same max and both got the same number — a classic
read-then-increment race. Fixed by allocating card numbers from an atomic
per-board counter (Counters.incrementCounterAsync('cardNumber-<boardId>')in
models/counters.js), a single atomicfindOneAndUpdate($inc)that is safe under
concurrency. Existing boards (and imported boards with existing cards) are
lazy-seeded on first use: the counter is initialized to the board's current
maxcardNumbervia an idempotent$setOnInsertupsert, so no migration is
needed and no number an existing card already has is reissued. Card numbers may
have gaps (deleting a card does not decrement the counter), which is intended.
Numbering stays per-board. The client keeps the old max + 1 read (card numbers are
not authoritative there; the server insert recomputes). See also #4743 below.
Thanks to xet7 and Claude. - Copy/Move Swimlane/List/Card to the same or a different board, before/after a
position counted (number of swimlanes/lists/cards) from the top-left.
Implemented: target position is a 0-based index counted from the top-left
("after N items"); the server converts it to asortvalue between siblings via
computeSortForIndex(server/lib/utils.js). New endpoints:
POST /api/boards/:boardId/lists/:listId/cards/:cardId/copy,
POST /api/boards/:boardId/swimlanes/:swimlaneId/copyand.../move,
POST /api/boards/:boardId/lists/:listId/copyand.../move. Copy is a FULL
deep copy via the existing modelcopy()methods (cards, checklists,
attachments, custom-field values). Destination board write access is required
(may differ from source). Wrapped inapi.pyascopycard,
copyswimlane/moveswimlane,copylist/movelist. NOTE:List.movemerges
into an existing same-titled list on the destination board when one exists (a
pre-existing model behavior); same-board list move is a pure reposition.
Thanks to xet7 and Claude. - Issue #4815: API to get My Cards and
Due Cards (also needs a user-scoped API).
The web UI already has My Cards and Due Cards views. Implemented: a single
GET /api/user/cardsendpoint returning the current user's cards (where they are
a member or assignee), with a?due=truefilter for cards that have a due date
and an optional?from=/?to=(ISO 8601) due-date range. Returns a compact
minicard-like field set, sorted by due date. Wrapped inapi.pyasmycards.
(Cross-user/admin querying, board filter and pagination were not requested for the
first version; revisit if needed for users in very many boards.)
Thanks to xet7 and Claude. - Issue #4811: After adding a card via
the API, does the card count update properly?
To verify:GET /api/boards/:boardId/cards_countand
GET /api/boards/:boardId/lists/:listId/cards_countafterPOSTcard creation.
Card counts are computed from the cards collection, so they should reflect
API-created cards; this needs a regression test confirming counts update
immediately after API create/delete (and are not stale due to caching). - Issue #3062: API for the Card
Settings that live under Board Settings.
Board Settings card defaults (which fields/badges show on cards and minicards) are
the board'sallows*toggles. Implemented:GET/PUT /api/boards/:boardId/cardSettingsexposing those board-levelallows*settings
(read requires board access; write requires board write access).PUTaccepts any
subset of the recognized keys and ignores unknown ones. Per-user card presentation
settings are intentionally out of scope. Wrapped inapi.pyasgetcardsettings/
setcardsetting.
Thanks to xet7 and Claude. - Fix Issue #4743: Using the REST API
to manipulate many cards crashes WeKan (100% CPU, server becomes
unreachable/unusable, REST calls time out with HTTP 408).
Reported workflow: every night delete all cards in a board and recreate them
(create card → edit with custom fields/dates/labels → list all → delete all),
done in a tight loop with no delay; WeKan eventually pegs CPU and stops
responding. Same root area as #5813 (concurrent card creation), plus the cost of
per-card cascade work (activities, server-side reactivity, custom-field defaults,
before/after hooks) under bursts.
Implemented so far: (1) atomic card numbering (see #5813) removes the hot
read-modify-write contention; (2) bulk create/delete endpoints
POST /api/boards/:boardId/lists/:listId/cards/bulk(body
{authorId, swimlaneId, cards:[...]}) and
DELETE /api/boards/:boardId/cards/bulk(body{cardIds:[...]}), each capped at
500 items per request, so a sync job sends one request instead of hundreds —
directly addressing the reporter's nightly delete-all/recreate workflow. Wrapped in
api.pyasbulkaddcards/bulkdeletecards. Bulk create returns a per-card
result/error array; bulk delete returns{deleted, notFound}and only touches
cards on the given board.
Still planned/optional: per-token rate limiting and batched/optional activity
logging were considered but not chosen for now; the bulk endpoints plus the atomic
card numbering are expected to remove the hot contention. Revisit rate limiting if
storms persist.
Thanks to xet7 and Claude. - Fix REST API auth bugs found by the new E2E tests: awaited the membership/admin
checks and return explicit status codes (CWE-862). Several auth helpers
(checkAdminOrCondition,checkUserId) are async, so calling them without
awaitlet a denied caller's rejection slip past while the handler kept running:
on card create that performed the write anyway (auth bypass), and on the board
member endpoints the un-awaited rejection surfaced to the client as an HTTP 503.
Fixes:- The pre-existing single-card create (
POST .../cards) and the new bulk-create
endpoint nowawaitthe membership check, so a logged-in non-board-member can
no longer create cards. - The board member add/remove/permission endpoints
(POST .../members/:userId/add,.../remove,POST .../members/:memberId)
now require board admin (or site admin), awaited, returning a clean 401/403
— instead of the previous un-awaitedcheckUserId(nominally site-admin-only,
but effectively bypassed) that 503'd on denial. - Board-label creation (
PUT .../labels) is gated to board admin / site admin,
and it plusGET /api/user/cardsreturn explicit 401/403 instead of letting an
auth helper throw (which, under Express 4, would otherwise leave the request
hanging). These were all found by the new REST API E2E test below. Thanks to
xet7 and Claude.
- The pre-existing single-card create (
- End-to-end tests for the new REST API behavior and permissions. A new
Playwright spectests/playwright/specs/17-rest-api.e2e.jsexercises the new/changed
endpoints against a running server (real Bearer-token auth → real HTTP request →
MongoDB change verified directly), asserting both correct data AND correct
permissions: bulk create/delete (#4743), unique card numbers under bulk create
(#5813), bulk label merge + rejection of off-board labels + BoardAdmin-gated label
creation (#5819), add board member by named role + invalid-role rejection and
card-member add validated against board membership (#5998), add/clear card date
(#5846), linked-card creation (#5897), copy-card-to-position deep copy, board card
settings GET/PUT (#3062),GET /api/user/cardswith due filter (#4815), and that an
unauthenticated request is denied. These use Playwright's HTTPrequestclient (no
browser is launched, so they run fast). The pure data/permission helpers they rely
on live inserver/lib/utils.js(boardMemberRoleToFlags,computeSortForIndex,
mergeLabelIds,canAssignCardMember,isCardDateClear). rebuild-wekan.shtest menu reorganized. There is now a menu option per test
type so each can be run on its own: "Run ALL tests on http://localhost:3000 (start
server, progress + summary)" (starts the server and runs import regression + Mocha +
Node E2E + Playwright Chromium, streaming progress and printing a per-suite PASS/FAIL
summary), plus standalone "Test Mocha unit + security + API-logic tests", "Test import
regression", "Test Node E2E regressions", and the existing per-browser Playwright
options. Playwright holds the browser-UI specs (01–16) and the API E2E spec (17);
the fast unit/security/policy tests stay in the Mocha suite (meteor test --once --driver-package meteortesting:mocha).
Thanks to xet7 and Claude.- Fix tests.
Thanks to xet7 and Claude.
Thanks to above GitHub users for their contributions and translators for their translations.
Security Fixes
- CVE-2026-TokenBleed – Fixed unauthenticated login‑token minting via missing `await` on async auth checks in `/api/createtoken/:userId` and related endpoints (CWE‑863, CWE‑287).
Weekly OSS security release digest.
The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.
No spam, unsubscribe anytime.
Share this release
About wekan
The Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at https://wekan.fi/commercial-support/ . New English strings for new features at imports/i18n/data/en.i18n.json . Non-English translations at https://app.transifex.com/wekan/wekan only.
Related context
Related tools
Beta — feedback welcome: [email protected]