The Mocha (server-side) test bundle crashed at boot with 0 tests run because
yargs and its ESM dependencies are dragged into the Meteor test server bundle,
where Meteor 3 wraps every module in a CommonJS function and their native-ESM
constructs (import.meta, `const __dirname`, `const require = createRequire(...)`)
are illegal and crash the bundle before any test runs.
Two gaps remained in scripts/patch-yargs-dirname.cjs:
- The `const require = createRequire(...)` removal (and its trigger) only matched
the direct-call form. yargs-parser/build/lib/index.js uses the guarded ternary
`const require = createRequire ? createRequire(import.meta.url) : undefined;`,
which slipped through and crashed at boot with "Identifier 'require' has already
been declared". Generalized the match to line-wise `const require = ...createRequire...;`.
- The scan only walked node_modules/yargs, so yargs-parser (the actual offender)
was never patched. Broadened the scan to yargs and its ESM dependency packages.
Also register the three server test files that were present but missing from the
curated server/lib/tests/index.js (they never ran): the MimeBleed file-validation
bypass regression, the import/export privacy settings, and the impersonation report
query. Server suite now boots and runs (411 passing).
CHANGELOG Upcoming documents the bug and the two-stage debugging journey.
- client/lib/tests/renderLinksPlainText.tests.js: the 'render links as plain text'
toggle and XSS hardening. Config assertions (stripLinks adds <a> to FORBID_TAGS,
default does not, KEEP_CONTENT) plus real sanitizeHTML() behavior: <a> stripped to
its text when stripLinks, kept by default, and javascript: hrefs / event handlers /
<script> removed regardless.
- client/lib/tests/alwaysShowCodeAsText.tests.js: escapeHtmlSource (exposed on the
Markdown export) escapes <script>/comments/quotes/ampersand-first so code is shown
not executed; leaves plain text and empty strings unchanged.
- server/lib/tests/impersonationReportQuery.tests.js: the Impersonation report search
selector (extracted to models/lib/impersonationReportQuery.js so it is testable
without re-registering the publication) builds a case-insensitive $or across the
identity fields, escapes regex metacharacters, and is empty for a blank term.
The MimeBleed fix (fileValidationBypass.security.tests.js), SortBleed
(boards.security.tests.js) and import/export anonymization
(importExportSecurity.tests.js) already carry positive + negative tests.
WeKan's upload validation (models/fileValidation.js) detects a file's real MIME
type by running the Unix 'file' command. On minimal Docker/Alpine images without
'file', detectMimeFromFile() silently returned undefined and the code fell back to
the CLIENT-supplied fileObj.type. An authenticated board member (WITH_API=true)
could upload HTML+JS while setting fileType: image/png; the spoofed type is not on
the dangerous-MIME deny-list, so the dangerous-content scan was skipped and the file
was stored, yielding stored XSS served under the WeKan origin (CWE-434).
Fix: the client-supplied type can no longer gate the safety scan. When content-based
detection via 'file' is unavailable, fall back to a dependency-free JS content sniff
(looksLikeDangerousMarkup) that inspects the real bytes for HTML/SVG/XML/<script>
signatures and forces the dangerous-content scan regardless of the claimed MIME, so a
spoofed image/png that is really HTML+JS is rejected. The sniff matches only definitive
markup, so genuine binary uploads (real PNG/JPEG/PDF, incl. large ones) are unaffected.
Also log a one-time warning when 'file' is missing (previously silent).
Regression test server/lib/tests/fileValidationBypass.security.tests.js covers the
spoofed dangerous uploads and safe binaries.
CVSS:3.1 8.3 High (AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N). Thanks to HNUfwj and xet7.
- server/lib/tests/importExportSecurity.tests.js: positive and negative unit
tests for the anonymization helpers (map building, user-doc scrubbing, @mention
rewriting that ignores unknown handles and email addresses, requestedBy/assignedBy
identity replacement, and safe no-ops on empty input).
- docs/Features/Admin-Panel/Admin-Panel.md: new Features tab section documenting the
Performance, Security (rich-text hardening + import/export privacy) and
Notifications (GDPR) toggles.
A low-privilege (comment-only / read-only) board member could escalate to
board admin and take over a private board via the board 'sort' collection
allow rule. To support drag-to-reorder, a second Boards.allow({ update })
rule returned true for any board member whenever the update touched the
'sort' field. Meteor applies the WHOLE modifier once any allow rule approves
and no deny rule rejects, and does not scope the approving rule to the field
that satisfied it. Because canUpdateBoardSort only checked that 'sort' was
AMONG the modified fields, a restricted member could smuggle arbitrary board
mutations (members, permission, title) into the same $set as 'sort' in one
DDP Boards.update call, making themselves admin, flipping the private board
to public, and evicting the legitimate owner. The last-admin deny rule only
inspected $pull, so a wholesale $set of the members array bypassed it.
Fix: restrict canUpdateBoardSort so the sort-reorder rule approves an update
only when 'sort' is the sole modified field. Defense in depth: the last-admin
deny rule now also rejects a $set rewrite of the members array that would
drop the last active admin. Regression test covers the multi-field smuggling
case. The legitimate All Boards drag-reorder is unaffected: it persists order
per-user in profile.boardSortIndex, not in the board document.
CVSS:3.1 8.8 High (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H), CWE-863, CWE-269.
Thanks to 5ud0 / Tarmo Technologies and xet7.
server/lib/tests/dependencies.openapi.tests.js is an ES module (top-level
import) that referenced the bare __dirname global. Under Node 24 / Meteor 3.5
the compiler injects "const __dirname = fileURLToPath(import.meta.url)" for
ESM files that use __dirname, which collides with the __dirname the CommonJS
module wrapper already provides, so the whole server bundle failed to boot
with "Identifier '__dirname' has already been declared" and the "Meteor unit
tests" CI job died before any test ran. Drop the __dirname seed
(process.env.PWD already reaches the repo root under `meteor test`, and the
suite skips gracefully otherwise).
Thanks to xet7.
- imports/i18n/i18n.test.js crashed the whole run at load: chai 6.x plugins (sinon-chai,
chai-as-promised) are ESM-only, so use(require('sinon-chai')) handed chai.use() a module
namespace instead of the plugin function ("fn is not a function"). Now imports the default
export.
- That unmasked a second load crash - PositionHistory.helpers is not a function: the
meteor test entry (server/lib/tests/index.js) never ran the .helpers / .attachSchema shim
that server/main.js bootstraps, so the first model to call Collection.helpers({...}) threw.
The shim is now imported first in the test entry.
- With the suite finally running, 44 server tests failed because meteor test only loads what
the specs import (not the app's /server/imports). The specs now import the files that
register the methods / globals under test (cards.vote / cards.pokerVote, api.attachment.*,
cloneBoard, getBackgroundImageURL, applyListWidth, updateListSort, moveChecklist,
userPositionHistory.*, archiveBoard, sendSMTPTestEmail, and the Attachments global). Also
fixed genuine test bugs: the cards.vote / cards.pokerVote specs were written synchronously
against async methods; the header-login trust specs restored env vars with
process.env.X = undefined (which stores the string "undefined" and shadowed the trusted-IP
allowlist, making every trusted source read as untrusted); the DnsBleed decimal-loopback
matcher assumed the integer host survived URL normalisation; and the dependencies-OpenAPI
spec could not locate its source file from the built bundle. The two cards.archive /
cards.move specs tested Meteor methods that do not exist (archive / move are Minimongo
document helpers secured by Cards.allow / Cards.deny, already covered by the cards security
tests) and were removed. Server-side Mocha is now 409 passing, 0 failing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The #6412 mocha test imported the helpers from fileStoreStrategy.js, which pulls
in Meteor, so it could only run under `meteor test` (and the local harness loaded
0 server tests here) — meaning it could not be negative-tested locally.
Extract the pure, Meteor-free helpers (truncateFilenameToBytes / sanitizeFilename
/ MAX_FILENAME_BYTES) into models/lib/filenameSanitizer.js; fileStoreStrategy.js
requires them. Add tests/filenameSanitizer.test.cjs (plain Node, `npm run
test:unit:node`) and point the mocha test at the new module. The Node test is
negative-tested: 7 passing on the fix, and the length-cap assertion fails when
the truncation is reverted. Verified `meteor build` still compiles (the helper
is bundled into the server).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Export truncateFilenameToBytes / sanitizeFilename from fileStoreStrategy and
add server/lib/tests/filenameTruncation.tests.js (#6412): byte-budget
truncation preserves the extension, never splits a multibyte codepoint, caps
the on-disk component under 255 bytes, and still strips path traversal.
- Spec 36: a guest opening a public board renders lists with no
"isBoardAdmin of null" crash (#3897).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Posting a comment without the required `comment` parameter (or to a board
that does not exist) returned an HTTP 500 error page. The schema-validation
error thrown on insert is a circular object (SimpleSchemaValidationContext ->
SimpleSchema -> ...), and serializing it crashed the response writer with
"Converting circular structure to JSON".
- Add Meteor-free helpers (server/lib/apiResponseHelpers.js): safeJsonStringify
(crash-proof, falls back to {error} on circular structures), httpStatusForError
(maps to 401/403/404/500), validateCommentBody, extractErrorMessage.
- sendJsonResult now serializes via safeJsonStringify so a non-serializable
payload can no longer crash into a 500.
- checkBoardAccess/checkBoardWriteAccess/checkBoardAdmin throw a 404 instead of
a TypeError on board.members when the board does not exist.
- POST comment validates input (400 when comment missing); REST comment catch
blocks return the real status code instead of 200.
- Add 18 unit tests (server/lib/tests/apiResponseHelpers.tests.js).
- Update CHANGELOG upcoming.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add Upcoming 'fixes the following bugs' bullets (with commit links) for the
subtask fixes (#3868/#5788/#2256/#4782, #4037/#3562, #3328, #3414/#3876/#4849/#4947,
#3743) and webhook fixes (#1587/#2950, #1402), register their unit tests, and add
the act-deleteCard / act-removeBoard / act-removeList / act-removeSwimlane i18n
keys so the delete activities render in the feed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Subtask creation is now server-authoritative via a new addSubtaskCard Meteor
method: the default subtasks board/list/swimlane are resolved (and lazily created
exactly once) on the server, and client-side auto-creation in
getDefaultSubtasksBoardId/getDefaultSubtasksListId is guarded to Meteor.isServer.
This stops the client from creating duplicate subtasks boards/swimlanes/columns
(#3868/#5788/#2256) and lets multiple subtasks be created reliably (#4782). The
method applies the destination board's automatic custom fields to the new
subtask (#4037/#3562). The circular-parent guard now compares ids by value
(includes()) instead of array indices, and setParentId refuses a cyclic
re-parent (#3328). Pure helpers (wouldCreateCycle, subtaskCustomFields) moved to
isomorphic /imports/lib so the client bundle no longer imports a server module.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deleting a card created no Activity (only archiving did), so the
Activities.after.insert outgoing-webhook hook never fired on real deletion.
Added a deleteCard Activity in the Cards before.remove hook and in the two REST
card-delete endpoints (which use Cards.direct.removeAsync and bypass the hook),
so an outgoing webhook is now sent on card deletion, consistent with other
events. (#2950: board/list/swimlane deletion already emit removeBoard/removeList/
removeSwimlane activities, so those webhooks were already present.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The board Subtasks settings popup compared the selected list against
subtasksDefaultBoardId instead of subtasksDefaultListId (so the saved list was
never highlighted and looked unsaved/wrong), and listed the current board's
lists rather than the configured deposit board's. The dropdown now reads the
deposit board's lists and matches the stored subtasksDefaultListId, and changing
the deposit board resets the stale list id. resolveSubtaskLanding helper tested.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Setting a card member creates an Activity, and the Activities.after.insert hook
delivered the outgoing webhook in a way that could throw (e.g. unreachable/slow
endpoint) and unwind the async hook, aborting the member update. Webhook delivery
is now fire-and-forget and wrapped in safeDeliver(), which catches and logs any
error and never rejects, so a failing webhook can't break the member operation.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add Upcoming 'fixes the following bugs' bullets (with commit links) for the latest
batch of open Bug fixes (#3289/#4172/#5457, #5398/#5399/#5537/#5546, #5408, #5382,
#5582, #5330, #2091), register their unit tests in the loaders, and rewrite the
#3265 Latvian-language regression test to import the languages module directly
(the previous fs.readFileSync approach failed in the Meteor test bundle). Latvian
native name 'Latviešu valoda' was already correct, so #3265 needed no code change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A number-type Custom Field rendered the literal 'NaN' once its value was cleared.
The submit handler now stores '' instead of NaN for empty input, and a shared
formatNumberValue helper normalizes empty/null/non-numeric values to an empty
string for display. Helper unit-tested.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lists.setColor / Swimlanes.setColor wrote the raw picker value with no normalization,
so an offered-but-unhandled colour fell back to None. Added a shared canonical
allowed-colour helper (normalizeListColor / ALLOWED_LIST_COLORS, including silver)
used by both setters: valid colours (incl. silver) pass through, unknown/empty
normalize to None (null). Removed leftover debug console.log in listHeader.js.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 'Hide checked items' state is now read per checklist (from that checklist's own
hideCheckedChecklistItems field) and an item is hidden exactly when it is checked
and that checklist's toggle is on — so toggling one checklist no longer affects the
others and the direction is no longer inverted. Pure isItemHidden helper unit-tested.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- #5398: consolidate duplicated/inconsistent board-move variable names in the card
edit handler into one normalized set (normalizeMoveParams); API contract unchanged.
- #5399: moving a card to another list via the API now places it on TOP of the
destination list (computeTopSort), matching the Move Card dialog, instead of a
random sort position.
- #5537: due/received/start/end dates set via the API now persist — the ISO string
is parsed to a real Date (parseCardDate) instead of being stripped by schema cleaning.
- #5546: the single-card GET returns archived cards (for inspection) and PUT
archive=false no longer requires a list_id the caller cannot obtain.
Pure helpers (normalizeMoveParams/computeTopSort/parseCardDate) are unit-tested.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Internal Subtasks-style boards with caret-wrapped titles (e.g. `^Subtasks^`) and
non-`board` types (`list`/`template`) leaked into user-facing board lists and the
REST API (/api/users/:userId/boards, /api/boards). Added a shared pure filter
(server/lib/boardListFilter.js) excluding caret-wrapped titles and non-board
types, applied to those REST endpoints, and added caret exclusion to the
Boards.userBoards model helper, consistent with the already-filtered board-list UI.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Board.removeMember only deactivated a member by array index and never pulled the
entry, so a member whose user account was deleted lingered forever and could not
be removed (also breaking @mentions). removeMember now hard-removes the entry by
userId when the Users document no longer exists, via the new pure pullMemberById
helper. Permission checks and existing-user deactivate/keep-last-admin behaviour
unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add Upcoming changelog bullets (with per-fix commit links) for the bugs found
still-present during Bug-issue triage and now fixed (#2970#5364#4561#5810#4762#5188#5623#5158#5521#5504#3619), register their unit tests in the
loaders, and add the activity-changedTitle i18n key.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Title changes did not create an activity, so no outgoing webhook fired (unlike
description/dueAt changes). Log an a-changedTitle activity in the Cards
before.update hook, mirroring the timing-field activities, so the existing
Activities.after.insert webhook hook fires. Pure titleChanged helper + tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The deleteAttachment activity used fileObj.userId (the uploader) instead of the
user who deleted the attachment, so notifications/history named the wrong person.
Record the acting user (Meteor.userId()) for the delete activity, falling back to
the uploader for server/system removals. Upload activity is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Swimlane copy() remapped card labels by name but never created labels missing on
the destination board, so those assignments were silently dropped. Pre-create
missing destination labels (preserving color) before the per-card copy, so the
by-name remap matches. Pure remapLabelIds helper + tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
allCards() filtered by listId only, so list select-all selected cards across all
swimlanes of the list. Add an optional swimlaneId scope (used in swimlanes view)
so select-all is contained to the current swimlane; list-wide behavior is
preserved when there is no swimlane context. Orphan cards remain selectable.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The .js-archive handler in cardDetails.js lacked the Utils.canModifyCard() guard
that every other mutating card action has, so a comment-only member could trigger
archiving in the UI (the server allow-rule already rejected the write, so this
closes the client-side UX gap). Guard added via pure canArchiveCard helper.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cards.copy() remapped labels by name without the unnamed-label guard that
Cards.move() has, so unnamed destination labels were all wrongly selected; it
also wrote the remapped labels onto a throwaway cardData copy instead of the
inserted document. Apply the move()-style guard via shared filterCopiedLabelIds
and persist onto the inserted card. Pure helpers + tests in
server/lib/cardCopyHelpers.js and server/lib/tests/cardsCopyDueFixes.tests.js
(which also cover #4561 and #5364, committed next).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New dependency-free iCalendar parser (server/lib/icsImport.js) maps each VEVENT
to a card with startAt/dueAt so events show on Calendar/Gantt views, plus an
importIcsToBoard Meteor method. Import-only; two-way Google Calendar sync is not
included. Parser unit tested (folded lines, escaping, all-day vs date-time, etc.).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Uploading an attachment now creates an addAttachment activity, so card members
and subscribers are notified (consistent with attachment removal) and it appears
in card history. Previously the activity was never created because the store
strategy upload hook was dead code. buildAttachmentAddedActivity unit tested.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comments gain an optional parentId with a Reply link and an 'in reply to' quote
(#5907, MVP single-level threading). New board setting restrictCommentEditing
(default off): when on, only a comment's author may edit/delete it, enforced
server-side via collection hooks (#5906). canEditComment decision unit tested.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A user can permanently close the current global announcement so it does not
reappear on reload or board-switch, until the admin edits the announcement text
(its content-derived version changes, so it reappears for everyone). Dismissed
version stored on the user profile; visibility decided by the unit-tested
shouldShowAnnouncement helper.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New global admin setting boardMembersFromSameOrgOrTeamOnly (default off). When
enabled, a user can only be added to a board if they share an Organization or
Team with the inviter or an active board member; enforced server-side in the
invite and user-search paths. Site admins bypass. Pure canAddUserToBoard helper
with unit tests; Admin Panel toggle added.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Display a 'due date changed N times' count on the card detail (derived from
existing a-dueAt activities via getDueDateChangeCount) for deadline
accountability. Counting logic extracted to server/lib/dueDateEdits.js with
unit tests. MVP per the issue (display rather than hard limit).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Board-level search now matches text inside card comments, consistent with global
search (#5910); the (title OR description OR comment) rule is extracted to the
unit-tested helper server/lib/cardMatch.js. Search-result cards can be dragged
directly onto board lists, reusing card.move() (#6034, MVP: drops append to end
of the target list).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Checklists and ChecklistItems collection allow rules authorize an
update only against the document's CURRENT (source) cardId. A DDP client
with write access to its own board could move a checklist/item it owns
onto a card in a private board it is not a member of by $set-ting a new
cardId (and, for items, checklistId); the before.update hook then
denormalizes boardId from the destination card, attaching the
attacker-controlled document to the victim's private board. Same class
as denyCrossBoardMove / BoardBleed (GHSA-gm7v-pc38-53jr), but for the
card-attached checklist documents that the boardId-only deny did not
cover.
Add denyCrossBoardMoveByCard and denyCrossBoardMoveByChecklistItem in
server/lib/utils.js, which reject any update whose destination board
(resolved from a new boardId, cardId, or checklistId in the modifier)
the caller cannot write to, and wire them into Checklists.deny /
ChecklistItems.deny. Legitimate moves into writable boards and same-card
edits are unaffected; the server-side moveChecklist Meteor method bypasses
allow/deny and is unchanged. Adds a regression test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- server/lib/tests/cards.dependencies.move.tests.js: stub-based unit tests of
the card.move() helper — a cross-board move clears the moved card's
cardDependencies and issues a multi-update $pull to remove inbound references
on the old board; a same-board move leaves dependencies untouched.
- server/lib/tests/jiraCreator.dependencies.tests.js: JiraCreator.createDependencies
maps Jira issuelinks to typed dependencies (outward/inward "Blocks" ->
blocks/is-blocked-by, others -> related-to), normalizes color/icon, and skips
self-links, unknown targets and link-less issues.
- Registered both in server/lib/tests/index.js; CHANGELOG note updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Turns the flagged manual checks into automated tests and covers the newest
dependency ("Red Strings") features that lacked tests.
Mocha (server/lib/tests, registered in index.js):
- dependencies.metadata.tests.js: normalizeDependency (legacy string upgrade,
defaults, invalid type), direction meta (blocks/fixes forward,
is-blocked-by/is-fixed-by reversed, related-to undirected), inverse map.
- dependencies.openapi.tests.js: every WebApp.handlers dependency route has
@operation/@tag Dependencies/@summary, no undocumented route, unique ids
(guards "OpenAPI picks up new routes" without running the python generator).
Mocha (client/lib/tests):
- filter.dependencies.tests.js: Filter.cardDependencies toggles and builds a
cardDependencies.type mongo selector.
Playwright e2e:
- 28-dependencies-rest.e2e.js: REST CRUD with type/color/icon, board-level
list, update-in-place, validation (bad type, self-link), auth — exercises the
cardDependencies SimpleSchema via real $push and positional ($) updates on the
live server (the schema manual-check).
- 27-red-strings.e2e.js: added minicard badge, copyCard same-board preservation
(type/color), and importBoardDependencies card matching by number and title.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Derive the header-login source IP from the real TCP socket peer instead of the
spoofable X-Forwarded-For header, fail closed when the trusted-IP allowlist is
unset, and add opt-in multi-proxy support via HEADER_LOGIN_TRUSTED_PROXIES
(honor XFF only from a trusted proxy peer, taking the right-most untrusted hop).
Propagate the new env vars across docker-compose, start-wekan.sh/.bat, Dockerfile,
.devcontainer, snap config and the virtualbox script; add docs and regression tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
since after WeKan v7.93 and only generated docs for the `login`/`register`
endpoints (2 operations) instead of the full API. The Meteor 3 migration moved
the REST routes from `models/*.js` (`JsonRoutes.add(...)`) into
`server/models/*.js` (`WebApp.handlers.get/post/put/delete(...)`) and
introduced optional chaining (`?.`) that the `esprima` Python parser cannot
read, so `openapi/generate_openapi.py` silently skipped every route file.
The generator now understands both routing styles, scans both `models/` and
`server/models/`, downlevels modern JS syntax so files parse, handles the
`type: Array` SimpleSchema idiom, and `releases/rebuild-docs.sh` works directly
with Python 3.12.x (PEP 668). The generated `public/api/wekan.yml` /
`wekan.html` now cover the full API again (89 operations / 61 paths).
Thanks to Claude.
**Documented the attachment / file REST API in the OpenAPI docs** — file
upload, download, info, listing a board's files, copy, move and delete (the
`/api/attachment/*` endpoints registered via `WebApp.handlers.use()`, which the
generator cannot auto-discover) are now documented in `openapi/extra_paths.yml`
and injected into the generated docs, including their authentication and
permission requirements. Thanks to Claude.
**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 Claude.
**The attachment copy API now honours the admin "Admin Panel / Attachments"
API transfer limits.** Copying an attachment creates a new attachment but
skipped the `apiUploadBlocked` / `apiUploadMaxBytes` checks that upload
enforces; copy now respects them in both API implementations. Thanks to 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 (`<script>`, inline
`on*=` event handlers, `javascript:`/`vbscript:` URIs, `<foreignObject>` /
`<iframe>` / `<object>` / `<embed>` and similar active content) and XML loops
(`<!DOCTYPE>` / `<!ENTITY>` entity-expansion / XXE constructs and
`<?xml-stylesheet?>`), so SVG images can be uploaded safely.
Thanks to Claude.