| CVE | Vulnerability name | Date | Responsible Security Disclosure by | Vulnerabilities |
|---|---|---|---|---|
|
CloneBleed CVE-2026-53447 GHSA-qfqv-42qw-vvwh
|
CloneBleed |
2026-06-04 |
dizconnectz (cloneBoard) and Claude (similar issues)
![]() Did send detailed report with full PoC! |
|
The cloneBoard Meteor method in models/import.js copied an entire board —
including all cards, comments, attachments, member info and activities — identified solely by a
caller-supplied sourceBoardId, and performed no authorization check. It never verified
that the calling user was a member of (or otherwise permitted to read) the source board.
Any authenticated Wekan user who knew a board's ID (board IDs appear in board URLs and remain known to
removed members) could call Meteor.call('cloneBoard', '<targetBoardId>') over DDP and
obtain a permanent, fully-readable copy of that board's contents, even for private boards they had no
access to. The method called exporter.build() directly, skipping the
canExport() guard (models/exporter.js) that the REST export route correctly enforces.
The Meteor allow/deny rules in server/permissions/boards.js do not apply to server-side
method calls, so nothing else stopped it.
CVSS 6.5 (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N).
Fix: require this.userId and run the same exporter.canExport(user) check
the export route uses before building/cloning the board, so cloning a board now requires the same read
authorization as exporting it.
A review for the same class of bug found several server-side methods whose access checks were present in the source but never actually enforced, plus a few methods missing a check entirely:
server/models/checklists.js): the membership guard called
allowIsBoardMemberByCard(...), but that helper is async and was both unimported
and un-awaited, so !allowIsBoardMemberByCard(...) evaluated !Promise (always
false) and the check never ran. Now imported and awaited, and login is required.server/models/lists.js): the guard read
typeof allowIsBoardMember === 'function' && !allowIsBoardMember(...), but
allowIsBoardMember was never imported, so the typeof was always false
and the guard never ran. Replaced with a real hasBoardWriteAccess check, plus a check that the
list belongs to the named board, and a login check.server/attachmentApi.js, server/routes/attachmentApi.js):
the attachment Meteor methods and REST handlers called board.isBoardMember(...), which is not a
method on the board model. Replaced with the real board.hasMember(...), and the upload path now
also verifies the target card belongs to the named board (so boardId cannot be spoofed).server/models/users.js): stored per-board list width with no
authorization. Now requires the caller to be a member of the board and the list to belong to that board.server/models/boards.js): returned any board's background image
URL by ID. Now requires the board to be visible to the caller.server/migrations/fixMissingListsMigration.js) and
attachment migration (server/migrations/migrateAttachments.js): only checked that the
caller was logged in. Reading status now requires board visibility, executing a board-rewriting migration
requires board admin (or instance admin), and the attachment migration methods require board access (global
status reads require instance admin).server/notifications/outgoing.js): trusted the
caller-supplied integration object and only checked that some integration with that URL
existed. It now verifies a matching integration exists on its own board and that the caller is a member of
that board.server/models/userPositionHistory.js): the
createCheckpoint, getRecent, getCheckpoints and
restoreToCheckpoint methods only checked login, while the sibling
positionHistory.track* methods already require board visibility — the same
PositionHistoryBleed class. All four now require the board to be visible to the
caller before reading or restoring board-scoped position history.
The browser UI hides actions from read-only members and from non-admins, but those checks are cosmetic — the
server-side allow rules and Meteor methods must enforce the same role. An audit found four gaps:
server/permissions/customFields.js): used
allowIsAnyBoardMember (mere membership) for insert/update/remove, so read-only/comment-only/worker
members could create/modify/delete Custom Fields via direct DDP collection writes — the same read-only-write
class as ReadOnlyBleed (CVE-2026-52892 GHSA-6733-4wgq-8xvr), which had only fixed the REST path. Now uses
a write-access helper (allowIsAnyBoardMemberWithWriteAccess).server/permissions/cardCommentReactions.js): used
allowIsBoardMember, letting read-only members add/remove reactions. Now uses
allowIsBoardMemberCommentOnly, matching CardComments.insert.server/models/boards.js): required only board membership, so any member
(including read-only) could archive a board over DDP, although the UI gates archiving behind board admin. Now
requires board admin (or global admin), matching the Boards.allow update/remove rules.server/models/settings.js): required only login, so any authenticated
user could trigger an SMTP test (and have the server's SMTP error messages surfaced to them), although the UI
gates it behind global admin. Now requires global admin.| Timeline | Details |
|---|---|
| 2026-06-04 |
Report received from dizconnectz (CVE-2026-53447 GHSA-qfqv-42qw-vvwh). |
| Upcoming release | Fixed at Upcoming WeKan release, together with the similar issues found by code review. |