From 1c6d7fd9ddef0c4d809275b5a1104b013578abc7 Mon Sep 17 00:00:00 2001 From: TrueCharts Bot Date: Sat, 6 Jun 2026 13:48:56 +0200 Subject: [PATCH] =?UTF-8?q?feat(wekan):=20update=20image=20docker.io/wekan?= =?UTF-8?q?team/wekan=20v9.34=20=E2=86=92=20v9.35=20(#48816)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Update | Change | |---|---|---| | [docker.io/wekanteam/wekan](https://redirect.github.com/wekan/wekan) | minor | `894f63f` → `7f926f0` | --- > [!WARNING] > Some dependencies could not be looked up. Check the [Dependency Dashboard](../issues/18710) for more information. Add the preset `:preserveSemverRanges` to your config if you don't want to pin your dependencies. --- ### Release Notes
wekan/wekan (docker.io/wekanteam/wekan) ### [`v9.35`](https://redirect.github.com/wekan/wekan/blob/HEAD/CHANGELOG.md#v935-2026-06-06-WeKan--release) [Compare Source](https://redirect.github.com/wekan/wekan/compare/v9.34...v9.35) TLDR: - Admin Panel/Attachments/Move Files works: CollectionFS/Meteor-Files/S3/Azure Blob/Google Cloud Storage. - To have attachments and avatars visible, move them from CollectionFS to any other Storage - To upgrade, mongodump/mongorestore (older with [LD\_LIBRARY\_PATH](https://redirect.github.com/wekan/wekan/blob/main/docs/Backup/Backup.md#backup-wekan-snap-to-directory-dump)) to MongoDB 7.x, and copy WRITABLE\_PATH files/attachments/avatars if exists, at Snap /var/snap/wekan/common/files/, at Docker /data/files etc. This release adds the following [CRITICAL SECURITY FIXES](https://redirect.github.com/wekan/wekan/commit/357de728c03113b787065bac2c5832ad77f1a117): - [Fix GHSA-qfqv-42qw-vvwh: `cloneBoard` Meteor method has no authorization check — any user can clone (read) any private board by ID (CWE-639, CWE-862)](https://redirect.github.com/wekan/wekan/security/advisories/GHSA-qfqv-42qw-vvwh). 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', '')` 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. Fixed by requiring `this.userId` and running 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. CVSS 6.5 (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N). Thanks to dizconnectz for the coordinated disclosure, xet7 and Claude. and, while fixing the above, the following similar authorization issues found by code review were fixed (the [CloneBleed](https://wekan.fi/hall-of-fame/clonebleed/) group): - [Fix authorization guards that silently never ran in attachment, list, checklist, migration and webhook server methods (CWE-862, CWE-639)](https://redirect.github.com/wekan/wekan/blob/main/docs/hall-of-fame/clonebleed/). A review for the same class of bug as `cloneBoard` 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` `moveChecklist`: 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 — any logged-in user could move a checklist between cards on boards they cannot access. Now imported and awaited, and login is required. - `server/models/lists.js` `updateListSort`: the guard read `typeof allowIsBoardMember === 'function' && !allowIsBoardMember(...)`, but `allowIsBoardMember` was never imported, so the `typeof` was always `false` and the guard never ran — any caller could reorder/re-assign any board's lists by ID. Replaced with a real `hasBoardWriteAccess` check, plus a check that the list actually belongs to the named board, and a login check. - `server/attachmentApi.js` and `server/routes/attachmentApi.js`: the attachment Meteor methods and REST handlers called `board.isBoardMember(...)`, which is not a method on the board model, so the permission check never behaved as intended. Replaced with the real `board.hasMember(...)`, and the upload path now also verifies the target card actually belongs to the named board (so `boardId` cannot be spoofed to a board the caller is a member of). - `server/models/users.js` `applyListWidth`: stored per-board list width with no authorization. Now requires the caller to be a member of the board and requires the list to belong to that board. - `server/models/boards.js` `getBackgroundImageURL`: returned any board's background image URL by ID. Now requires the board to be visible to the caller. - `server/migrations/fixMissingListsMigration.js` and `server/migrations/migrateAttachments.js`: the migration status/execute methods only checked that the caller was logged in. Reading status now requires board visibility, executing a board-rewriting migration now requires board admin (or instance admin), and the attachment migration methods now require board access (global status reads require instance admin). - `server/notifications/outgoing.js`: the webhook delivery method 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, closing a path where any authenticated user could drive webhooks (and, via the two-way response path, overwrite comments) on boards they cannot access. - `server/models/userPositionHistory.js`: the `userPositionHistory.createCheckpoint`, `.getRecent`, `.getCheckpoints` and `.restoreToCheckpoint` methods only checked that the caller was logged in, even though the sibling `positionHistory.track*` methods (`server/methods/positionHistory.js`) already require the board to be visible to the caller — the same PositionHistoryBleed class fixed in v8.20/v8.21. All four now require board visibility (via the shared `isVisibleBy` guard) before reading or restoring position history scoped to a board. Thanks to xet7 and Claude. and, while auditing that client-side permission checks are also enforced server-side, the following gaps where the server did not re-verify a UI-gated permission were fixed: - [Fix client-side permission gates not re-verified server-side (CWE-862, CWE-269)](https://redirect.github.com/wekan/wekan/blob/main/docs/hall-of-fame/clonebleed/). The browser UI hides certain actions from read-only members and from non-admins, but those are cosmetic — the server-side `allow` rules and Meteor methods must enforce the same role. An audit found four places that did not: - `server/permissions/customFields.js`: the Custom Field `allow` insert/update/remove rules used `allowIsAnyBoardMember` (mere membership), so read-only/comment-only/worker members could create/modify/delete Custom Fields (board-wide schema) via direct DDP collection writes — the same read-only-write class as GHSA-6733, which had only fixed the REST path. Now uses the new `allowIsAnyBoardMemberWithWriteAccess` write-access helper. - `server/permissions/cardCommentReactions.js`: the reaction `allow` rules used `allowIsBoardMember`, letting read-only members add/remove comment reactions. Reacting is a form of commenting, so it now uses `allowIsBoardMemberCommentOnly` (Normal/Comment-only allowed, Read-only/No-comments denied), matching `CardComments.insert`. - `server/models/boards.js` `archiveBoard`: only required board membership, so any member (including read-only) could archive a board (hiding it for everyone) 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` `sendSMTPTestEmail`: only required login, so any authenticated user could trigger the server to send 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. Thanks to xet7 and Claude. and, in the same access-control audit, the attachment write API was tightened: - **Security: attachment write operations in the REST/DDP API now require board write access, not just membership (CWE-862, CWE-639).** Both attachment API implementations (`server/routes/attachmentApi.js` HTTP routes and `server/attachmentApi.js` Meteor methods) gated upload / copy / move / delete on `board.hasMember()`, which is true for any active member regardless of role. This let read-only, comment-only, no-comments, worker and assigned-only board members add, copy, move and delete attachments through the API — actions the UI forbids for those roles. They now require board write access (global site admins still allowed); read operations (download / list / info) keep membership-level access. Added security tests in `server/lib/tests/attachmentApi.tests.js`. Thanks to xet7 and Claude. and adds the following new features: - **[Admin Panel / Attachments](https://redirect.github.com/wekan/wekan/commit/a86a087915aac9d204e157b1a698091c079e04e6): "Calculate file counts" on every storage backend, shown right below the "Read" toggle.** Azure Blob Storage and Google Cloud Storage now have a "Calculate file counts" button (new `getAzureStorageStats` / `getGcsStorageStats` methods) that reports how many attachments and avatars are stored on that backend, matching what Filesystem, MongoDB GridFS and S3 already offer. The count is read from the stored file metadata (fast, no cloud API calls). The S3 button was also moved up to sit directly under its "Read" checkbox, so all backends are consistent. Thanks to Claude. - \*\*[Admin Panel / Attachments: the MongoDB GridFS page notes how to make legacy files visible](https://redirect.github.com/wekan/wekan/commit/dca9427bfb58532778bc705e4c8c8014ba4b01ae). \*\* A translatable line under the "MongoDB GridFS Storage" title reminds admins: "To have attachments and avatars visible, move them from CollectionFS to any other Storage." Thanks to Claude. - **Admin Panel / Attachments: the Google Cloud Storage page now documents the required bucket permission.** A note under the "GCS Storage" title explains that the service account needs the **Storage Object Admin** role (`roles/storage.objectAdmin`) on the bucket (Cloud Console → Cloud Storage → Buckets → your bucket → Permissions → Grant access → add the service account → role "Storage Object Admin"), and that the bucket must exist in the same project — otherwise "Test connection" fails with `storage.objects.list denied`. Thanks to xet7 and Claude. - **Admin Panel / Attachments: a "Save" button next to the Enabled / Read toggles at the top of each cloud storage.** S3, Azure and Google Cloud Storage now have a Save button directly below the "\[ ] Enabled \[ ] Read" row, so those toggles can be saved without scrolling to the bottom of the (now quite long) storage page. It saves the same way as the existing bottom Save button — the server merges that provider's config and preserves secrets left blank — and the bottom Save button is kept. (Filesystem and GridFS have only a Read toggle, which already saves the instant it is toggled, so they need no button.) Thanks to Claude. - **Admin Panel / Attachments: bilingual, self-documenting cloud-storage fields (S3, Azure and Google Cloud Storage).** Each cloud-storage setting now guides the admin from top to bottom with both English and translated text, so it is easy to follow along while clicking through the provider's Cloud Console: - the field **label** uses the provider's own console wording in English (e.g. *Access key ID*, *Secret access key*, *Storage account key*, *Service account key (JSON)*), with the translated label shown below it; - a literal **example** of what the value should look like (e.g. `eu-west-1`, an example bucket name / endpoint / connection string), which is intentionally not translated; - a short **description** of the value, in English and then translated; - the **Cloud Console menu path** showing exactly where to find or create the value (e.g. *AWS Console → IAM → Users → your user → Security credentials → Access keys → Create access key*; *Azure Portal → Storage accounts → your account → Access keys → key1*; *Google Cloud Console → IAM & Admin → Service accounts → … → Keys → Add key → Create new key → JSON*), again in English and then translated. The provider field names, examples and menu paths are kept in English so they match what the Cloud Console actually shows, while the descriptions and menu paths are also translatable; section titles, the enable/read/force-path-style checkboxes and the Test/Save buttons remain fully translated. Thanks to Claude. - **Admin Panel / Attachments / Move Attachment: "Repair file locations" button and a persistent "last move" message.** The new **Repair file locations** button scans all attachments and avatars and finds any whose recorded storage (`versions..storage` / `path` / `meta.gridFsFileId`) no longer matches where the binary actually is — left inconsistent by an interrupted or failed move — and fixes the database to point at the real location: it detects the binary in GridFS (by the `metadata.fileId` stamped on upload, recovering files whose GridFS id reference was lost) or on the filesystem (using the storage strategy's own thorough path resolution, so the repair agrees with what a real download/move would find even when `versions..path` is stale), then corrects `storage`, `path` and `meta.gridFsFileId` accordingly. Cloud-stored files are left untouched, and files found nowhere are reported as "Not found". The scan is streamed (memory-safe) and shows a per-scope searched / repaired / not-found summary. Separately, after a move finishes the page now keeps showing the last move operation — source → destination (scope) and the date/time as `YYYY-MM-DD HH:MM:SS` — so it is clear what was last done. Thanks to xet7 and Claude. - **SVG image uploads are now sanitized instead of rejected.** Uploaded SVGs (attachments and avatars) are cleaned in place in `onAfterUpload` via the new `models/lib/sanitizeSvg.js`, which removes JavaScript (`