Wekan
Open source kanban board application built with Meteor
Alternative to: trello
v9.32
2026-05-31This release fixes the following CRITICAL SECURITY ISSUES:
- Fix GHSA-hc3x-hq3m-663q: Server-Side Request Forgery (SSRF) via webhook integration URLs (CWE-918).
Wekan’s outgoing-webhook integrations let a board
admin store an arbitrary URL that is later fetched server-side, so a
caller-controlled URL pointing at an internal address (cloud metadata at
http://169.254.169.254/latest/meta-data/, loopback, RFC 1918 ranges, etc.) could be used to reach internal services or exfiltrate data.- Originally fixed at the delivery layer in v8.35 and v8.36 (the
IntegrationBleed fixes).
Fix IntegrationBleed (v8.35)
routed webhook delivery in
server/notifications/outgoing.jsthroughfetchSafe(server/lib/ssrfGuard.js), which validates the URL and blocks private/loopback IPs, and Fix RebindBleed of IntegrationBleed (v8.36) hardened it further to resolve DNS once, pin the connection to the validated IP, and block redirects — closing the actual request-time SSRF including DNS rebinding. - New in this release: the missing input-side validation the advisory
requested, added at the REST write paths in
server/models/integrations.js. Those endpoints accepted webhook URLs without a robust check:POST /api/boards/:boardId/integrationsrelied only on the schema’s regexcustom()validator (no DNS resolution, blind to a hostname that resolves to a private IP and to decimal/octal/IPv4-mapped-IPv6 encodings), andPUT /api/boards/:boardId/integrations/:intIdwrote the URL viaIntegrations.direct.updateAsync, which bypasses schema validation entirely, so updated URLs were never validated at the data layer. Both endpoints now run the DNS-awarevalidateAttachmentUrl()(models/lib/attachmentUrlValidation.js, the same validator already used for attachment imports) before storing the URL, rejecting private/loopback/link-local/reserved targets with HTTP 400. Together with the v8.35/v8.36fetchSafedelivery guard and the schema validator on client-side inserts/updates, webhook URLs are now validated on every write path and again at delivery. Thanks to Claude.
- Originally fixed at the delivery layer in v8.35 and v8.36 (the
IntegrationBleed fixes).
Fix IntegrationBleed (v8.35)
routed webhook delivery in
- Fix GHSA-7w2h-g83c-jqrp: Authorization bypass in
copyBoardDDP method allows any user to copy private boards (CWE-862). ThecopyBoardMeteor method inserver/publications/boards.jshad no authorization check: any logged-in user could copy any board by ID — including private boards they are not a member of — cloning all cards, checklists, custom fields, labels and rules, while the equivalent REST endpointPOST /api/boards/:boardId/copycorrectly required board admin. The method also looped over caller-suppliedproperties(for (const key in properties) board[key] = properties[key]) and copied them onto the new board, letting an attacker inject arbitrary fields such asmembers(to add themselves as admin of the copy) orpermission: 'public'(to expose the copy to everyone). The member-level fix shipped in v9.09 as part of the AuthBleed fixes (caller must be authenticated, board must exist, caller must be a board member, andmembers/permissionare stripped frompropertiesbefore the copy). This release tightens it further to full parity with the REST endpoint:copyBoardnow requires the caller to be a board admin (board.hasAdmin(this.userId), the same{isActive:true, isAdmin:true}condition the REST API enforces viacheckAdminOrCondition) rather than merely a member. - Fix GHSA-cv95-8h7c-2ffq: Missing authorization on OIDC Meteor methods allows privilege escalation to admin (CWE-269, CWE-862).
Six Meteor methods used internally by the OIDC login flow were registered as globally DDP-callable
with no authorization, while their non-OIDC counterparts require admin.
setCreateOrgFromOidcandsetOrgAllFieldsFromOidc(server/models/org.js),setCreateTeamFromOidcandsetTeamAllFieldsFromOidc(server/models/team.js) let any authenticated user create/rename/ deactivate/modify arbitrary organizations and teams — includingorgAutoAddUsersWithDomainName— bypassing the admin-only restriction. Most critically,groupRoutineOnLogin(packages/wekan-oidc/oidc_server.js) setsisAdminfrom caller-supplied group data, so withPROPAGATE_OIDC_DATAenabled any authenticated user could call it over DDP with{groups:[{isAdmin:true,forceCreate:true}]}and promote themselves to global admin;boardRoutineOnLogincould likewise add the caller to the default board. These six methods are only ever invoked server-side (viaMeteor.callAsync) during the OIDC handshake, where a fix that checksisAdmin/this.userIdwould break legitimate group/admin propagation (the user is not yet logged in or admin at that point). Fixed by rejecting any direct client/DDP invocation: a server-to-serverMeteor.callAsyncruns withthis.connection === null, whereas a client call has a non-null connection, so each of the six methods now throwsnot-authorizedwhenthis.connection !== null. The legitimate OIDC login flow is unaffected. Thanks to alexwaira for the coordinated disclosure, and Claude. - Fix GHSA-mp7g-hj5q-gxhq: OIDC Account Takeover via Unconditional Email-Based Account Merge in
Accounts.onCreateUserhook (CWE-287). TheonCreateUserhook inserver/models/users.jsunconditionally merged an incoming OIDC login into any existing Wekan account whose email or username matched the (attacker-controlled) OIDC claims — no ownership check, no email-verification check, no notification. An attacker who could present a matchingemail/usernameclaim (trivial on self-hosted Keycloak/Authentik, where theemail/email_verifiedclaims are attacker-settable) inherited the victim’s_id, boards, cards, attachments, API tokens and admin status, all during the attacker’s own first OIDC login with no victim interaction. Fixed to fail closed, mirroringLDAP_MERGE_EXISTING_USERS: matching is now by email only (never username); auto-linking is opt-in via the newOAUTH2_MERGE_EXISTING_USERSsetting (OFF by default, so the default deployment never merges); even when enabled the OIDC provider must assertemail_verified=true; otherwise the OIDC login is rejected withoidc-email-already-in-useinstead of merging. The provider’semail_verifiedclaim is now captured into the OIDC service data inpackages/wekan-oidc/oidc_server.js. Thanks to alexwaira for the coordinated disclosure, and Claude. - Fix GHSA-6733-4wgq-8xvr: Read-only board members could create/modify/delete Custom Fields.
(privilege escalation via read-level authz on write operations, CWE-862).
All six mutating REST handlers in
server/models/customFields.js(POST/PUT custom-fields, POST/PUT/DELETE dropdown-items, DELETE custom-fields) called the read-levelAuthentication.checkBoardAccessinstead of the write-levelcheckBoardWriteAccess, letting a board member with the read-only role (isReadOnly/isReadAssignedOnly) write Custom Field data via the REST API whenWITH_API=true. Replaced the check withcheckBoardWriteAccessin all six mutating handlers (the two GET handlers correctly stay oncheckBoardAccess), mirroringlists.js/swimlanes.js/cards.js. Thanks to Wernerina for the coordinated disclosure, and Claude. - Fix regression from the avatar RCE fix GHSA-35j7-h385-2q9g: external antivirus scanner broken (
asyncExecundefined). The avatar RCE fix renamedasyncExectoasyncExecFileinmodels/fileValidation.js, but the admin-configured external scanner command line still called the now-undefinedasyncExec, throwingReferenceError(swallowed by the catch) and making every upload silently fail validation whenever an external scanner was configured. Restored a shell-basedasyncExecused only for that admin-configured command line; MIME detection still uses the shell-freeasyncExecFile. Thanks to Claude. - Fix CodeQL 68: Polynomial regex DoS in Jade parser. Thanks to CodeQL and Claude.
- Fix CodeQL 69: Polynomial regex DoS in Jade parser, part 2.
Remove the redundant
$anchor from the interpolation regex incompiler.jsand bundledjade.js. The greedy[\s\S]*already matches to end of string, so dropping$keeps the same match while eliminating the backtracking that CodeQL alertjs/polynomial-redosflagged. Thanks to CodeQL and Claude. - Fix CodeQL 63: Incomplete string escaping or encoding in bundled
jade.js. In uglify-js’smake_string(bundled twice into jade’s browser bundle), the chosen quote was escaped in a trailingstr.replace(/'/g, "\\'")that CodeQL alertjs/incomplete-sanitizationflags for not escaping backslashes in that same call. Backslashes were already escaped in the earlier single-passreplace, so this was a local false positive, but the fix folds the quote escaping into that same pass (escaping both quotes), making the escaping atomic and complete while keeping the emitted string literals decode-equivalent. Thanks to CodeQL and Claude. - Fix CodeQL 67: Incomplete multi-character sanitization. Thanks to CodeQL and GitHub Copilot.
- Fix CodeQL 60: Useless regular-expression character escape. Thanks to CodeQL and GitHub Copilot.
- Fix CodeQL 58: Incomplete multi-character sanitization. Thanks to CodeQL and GitHub Copilot.
- Fix CodeQL 57: Incomplete multi-character sanitization. Thanks to CodeQL and GitHub Copilot.
- Fix CodeQL 56: Incomplete string escaping or encoding](https://github.com/wekan/wekan/commit/c5e42607af5a0a396c0c85dba3652b8be28a2ff3). Thanks to CodeQL and GitHub Copilot.
- Fix CodeQL 55: Incomplete string escaping or encoding. Thanks to CodeQL and GitHub Copilot.
- Fix CodeQL 48: Clear-text logging of sensitive information. Thanks to CodeQL and GitHub Copilot.
- Fix CodeQL 418: Clear-text logging of sensitive information. Thanks to CodeQL and GitHub Copilot.
- Fix CodeQL 419: Clear-text logging of sensitive information. Thanks to CodeQL and GitHub Copilot.
- Delete calendar demos, so that CodeQL stops complaining. Thanks to xet7.
- Fix CodeQL 35: Insecure randomness. Thanks to CodeQL and GitHub Copilot.
- Fix CodeQL 417: Workflow does not contain permissions. Thanks to CodeQL and GitHub Copilot.
and adds the following updates:
- Added test menu options.
“Test Playwright Chromium”, “Test Playwright Firefox” and “Test
Playwright Webkit” to
rebuild-wekan.shfor running the Playwright end-to-end test suite per browser. Thanks to xet7. - Copy wepica style of hide/show password to WeKan login and register pages. Thanks to Chostakovitch and xet7.
- Updated dependencies. Part 1, Part 2, Part 3, Part 4, Part 5, Part 6: Fork and update meteor-node-stubs, Part 7, Part 8. Thanks to developers of dependencies.
and adds the following new features:
- Add User board access roles to Admin Panel / People / Roles.
A new global “Roles” tab in Admin Panel / People lets a site admin choose which
board roles are allowed to invite users to a board (“Allow Invite to Board”).
Each board role has its own toggle — Board Admin, Normal, Worker, Comment only,
No comments, Only Assigned Normal, Only Assigned Comment, Read Only and Only
Assigned Read — plus an “All Board Members” master toggle that selects them all.
The policy is enforced server-side in the
inviteUserToBoardandsearchUsersmethods, and the add-member button in the board sidebar is shown only to roles the policy allows. Global Admin Panel users (site admins) always have all rights and cannot be restricted here; this is kept clearly distinct in code from the per-board “Board Admin” role. Secure default: only Board Admin and Normal may invite. Thanks to xet7.
and fixes the following bugs:
- Fix typos. Thanks to xet7.
- Fix iOS Chrome jQuery event handler runtime error. Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.