Wekan
Open source kanban board application built with Meteor
Alternative to: trello
About
Versions (100)
v9.36
2026-06-11v9.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 anasyncfunction, so its 401 (undefineduserId) and 403 (not an admin) throws become rejected promises rather than synchronous exceptions. The REST handlers inserver/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 affectedGET /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/boardsandPOST /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 in
server/models/*.js(usingWebApp.handlers.get/post/put/delete), their schemas inmodels/*.js, OpenAPI docs are generated byopenapi/generate_openapi.pyintopublic/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/addandPOST /api/boards/:boardId/members/:userId/remove(action=add/remove), andPOST /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 friendlyROLEname → 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). Whenroleis present it wins over the individual flags; an unknown role returns 400. Members are identified byuserIdonly. Wired into bothPOST .../members/:userId/addandPOST .../members/:memberId. Thanks to xet7 and Claude. - Add/Remove board member to card as card member or card assignee.
Already exists via
PUT /api/boards/:boardId/lists/:listId/cards/:cardIdwithmembers/assignees(array, or ” to clear).api.pynow wraps these assetcardmembers/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/:memberIdand.../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 uses
linkedId/type: 'cardType-linkedCard'. Implemented by reusing the existingPOST /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 via
PUTcard withreceivedAt,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 inserver/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/labelstaking{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>')inmodels/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 a
sortvalue between siblings viacomputeSortForIndex(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_countandGET /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’s
allows*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:[...]}) andDELETE /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 inapi.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 withoutawaitlet 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 spec
tests/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.