Wekan logo

Wekan

Open source kanban board application built with Meteor

Alternative to: trello


About Versions (100)

v9.36

2026-06-11

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.checkUserId in server/authentication.js is an async function, so its 401 (undefined userId) and 403 (not an admin) throws become rejected promises rather than synchronous exceptions. The REST handlers in server/models/users.js and server/models/boards.js called it without await inside a plain synchronous try/catch, which cannot catch a rejected promise, so the failed check never stopped execution. POST /api/createtoken/:userId then 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/boards and POST /api/boards/:boardId/copy. Fixed by awaiting every async Authentication check (and making the two non-async handlers async) 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 by exporter.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:

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 (using WebApp.handlers.get/post/put/delete), their schemas in models/*.js, OpenAPI docs are generated by openapi/generate_openapi.py into public/api/wekan.yml, and api.py is the Python CLI wrapper. api.py already 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 and POST /api/boards/:boardId/members/:userId/remove (action=add/remove), and POST /api/boards/:boardId/members/:memberId to change an existing member’s permission flags (isAdmin, isCommentOnly, isWorker, isNoComments, isNormalAssignedOnly, isCommentAssignedOnly, isReadOnly, isReadAssignedOnly). api.py now wraps these with a friendly ROLE name → flags mapping. Implemented: the API itself now also accepts a single named role (admin/normal/commentOnly/worker/readOnly/normalAssignedOnly/ commentAssignedOnly/readAssignedOnly/noComments), mapped to the permission booleans server-side by boardMemberRoleToFlags (server/lib/utils.js). When role is present it wins over the individual flags; an unknown role returns 400. Members are identified by userId only. Wired into both POST .../members/:userId/add and POST .../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/:cardId with members / assignees (array, or ” to clear). api.py now 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/$pull so 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 in api.py as addcardmember/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 existing POST /api/boards/:boardId/lists/:listId/cards route: when linkedId is present in the body the new card is created via Card.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 in api.py as linkcard. Thanks to xet7 and Claude.
  • Fix Issue #5846: Add/Remove card dates (Received/Start/Due/End). Setting dates already worked via PUT card 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.py wraps this as setcarddate ... 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. PUT card labelIds REPLACES the whole label set (wrapped as setcardlabels). Implemented: a multi-card bulk endpoint POST /api/boards/:boardId/cards/labels taking {cardIds, addLabelIds, removeLabelIds} that MERGES — existing labels are kept, removeLabelIds are dropped, addLabelIds are added, de-duplicated, across all listed cards in one request. addLabelIds are validated against the board’s labels (request rejected with the offending ids otherwise); cards not on the board are reported in notFound. Wrapped in api.py as bulkcardlabels. Board label creation via PUT /api/boards/:boardId/labels is 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 max cardNumber and returned max + 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 atomic findOneAndUpdate($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 max cardNumber via an idempotent $setOnInsert upsert, 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 sort value 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/copy and .../move, POST /api/boards/:boardId/lists/:listId/copy and .../move. Copy is a FULL deep copy via the existing model copy() methods (cards, checklists, attachments, custom-field values). Destination board write access is required (may differ from source). Wrapped in api.py as copycard, copyswimlane/moveswimlane, copylist/movelist. NOTE: List.move merges 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/cards endpoint returning the current user’s cards (where they are a member or assignee), with a ?due=true filter 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 in api.py as mycards. (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_count and GET /api/boards/:boardId/lists/:listId/cards_count after POST card 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/cardSettings exposing those board-level allows* settings (read requires board access; write requires board write access). PUT accepts any subset of the recognized keys and ignores unknown ones. Per-user card presentation settings are intentionally out of scope. Wrapped in api.py as getcardsettings/ 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.py as bulkaddcards / 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 await let 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 now await the 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-awaited checkUserId (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 plus GET /api/user/cards return 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.
  • End-to-end tests for the new REST API behavior and permissions. A new Playwright spec tests/playwright/specs/17-rest-api.e2e.js exercises 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/cards with due filter (#4815), and that an unauthenticated request is denied. These use Playwright’s HTTP request client (no browser is launched, so they run fast). The pure data/permission helpers they rely on live in server/lib/utils.js (boardMemberRoleToFlags, computeSortForIndex, mergeLabelIds, canAssignCardMember, isCardDateClear).
  • rebuild-wekan.sh test 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.