Wekan logo

Wekan

Open source kanban board application built with Meteor

Alternative to: trello


About Versions (100)

v9.74

2026-07-05

v9.74 2026-07-05 WeKan ® release

This release fixes the following CRITICAL SECURITY ISSUES of DnsBleed and ExcelBleed:

  • DnsBleed — SSRF filter bypass via DNS-resolving hostname in outgoing webhooks (GHSA-66m2-4wfr-c45p, CWE-918). Incomplete-fix follow-up to WebhookBleed (GHSA-hc3x-hq3m-663q) and IntegrationBleed / RebindBleed. The synchronous URL validator on outgoing-webhook (board Integrations) URLs in models/integrations.js blocks private/loopback/link-local IPs by regex-matching the URL hostname string and never resolves DNS. A public hostname that resolves to a blocked address — e.g. 169-254-169-254.nip.io169.254.169.254 (cloud metadata), 127-0-0-1.nip.io127.0.0.1, or any attacker-controlled domain with an A/AAAA record pointing at an internal IP — passes that string blocklist. The report notes the underlying weakness: string matching is not an SSRF boundary because it can’t see the resolved IP.

    • The reported PoC was already blocked at delivery by the earlier RebindBleed fix: outgoing webhooks are sent through fetchSafe (server/lib/ssrfGuard.js), which resolves the hostname, validates the resolved IP, pins the connection to it and blocks redirects — so 169-254-169-254.nip.io is rejected before any request is made. The REST write paths (POST/PUT /api/boards/:boardId/integrations) were likewise already hardened in WebhookBleed to run the DNS-aware validateAttachmentUrl() at input time.
    • This release completes the fix and removes the drift risk the advisory points at. The delivery guard previously resolved only IPv4 A-records (dns.resolve4), leaving it blind to AAAA (an IPv6-only internal target was merely fail-closed, and legitimate IPv6 webhooks were unreachable) and keeping a second, less-complete private-range block-list that could drift out of sync with the input-time one. fetchSafe now resolves both address families via dns.lookup({ all: true }) — the same resolver call the input validator uses — and validates every resolved IP through the single shared isIpBlocked block-list in models/lib/attachmentUrlValidation.js. The three previously-separate block-lists (schema regex, delivery guard, input validator) are now one source of truth, and the schema-level url validator is documented in code as a non-authoritative first-line UI check only.
    • Affected Wekan v8.36 and later (input-side string validator); the delivery-time SSRF boundary has been in place since v8.35/v8.36 (IntegrationBleed) and v9.32 (WebhookBleed). Reported by 4n207. Thanks to 4n207 and xet7!
  • ExcelBleed — broken access control in the Excel-export REST route lets any authenticated user export any private board (GHSA-mwq8-ccpm-r533, CWE-862 / CWE-639). Same un-awaited async-auth bug class as BFLABleed (48 REST endpoints, v9.22), CloneBleed (un-awaited allowIsBoardMemberByCard, v9.35) and TokenBleed. models/exportExcel.js called its access-control guard exporterExcel.canExport(user) without await. Because canExport is async, it returns a Promise (always truthy), so if (exporterExcel.canExport(user) || impersonateDone) was always true and exporterExcel.build(res) ran regardless of the guard’s real result — any authenticated user could download the full contents of any board (card titles + descriptions, lists, swimlanes, members, metadata) via GET /api/boards/:boardId/exportExcel, including private boards they are not a member of. The JSON export route (/export) was correctly awaited and returned 403 for the same non-member.

    • Fixed by awaiting the guard — if ((await exporterExcel.canExport(user)) || impersonateDone) — matching every other export route (models/export.js, exportPDF.js, exportExcelCard.js, import.js). The Excel-export route was the lone remaining un-awaited canExport call site.
    • Affected Wekan v9.57.0 (latest) and earlier; present at HEAD until this release. Reported by sec-reex (defensive research, responsible disclosure, read-only PoC). Thanks to sec-reex and xet7!

and adds the following updates:

and fixed the following bugs:

  • Updated tests. Thanks to xet7.
  • Fix parallel tests. Thanks to xet7.
  • Fix tests. Thanks to xet7.
  • Board Settings / Card Settings / Checklist item count (0/0) on minicard. Default: Off. Thanks to carl-unique and xet7.
  • Card Settings popup options combined to same lines, and added more options. Thanks to xet7.
  • Fix tests. Thanks to xet7.
  • Playwright E2E: fixed three cross-process-contention flakes in the parallel run. The Chromium / Firefox / WebKit browser jobs run as separate processes against ONE shared server + DB, so specs that used fixed identifiers or global cleanups raced each other:
    • 26-shared-templates.e2e.js seeded a fixed email domain (usera@acme-e2e.invalid) in all three browsers, hitting E11000 duplicate key on the unique emails.address index. Now uses a unique-per-run token for the org / team / domain and template titles.
    • 38-impersonation.e2e.js cleaned up with a global deleteMany({ reason: 'clickedImpersonate' }) that deleted another browser’s in-flight audit record mid-poll. Scoped the cleanup to the test’s own adminId.
    • 32-org-team-feature-toggles.e2e.js exercises setAllOrgsFeature / setAllTeamsFeature, which do a global updateMany({}) across every org / team, so two browsers clobbered each other’s rows. These browser-agnostic server-method tests now run in a single project (Chromium); Firefox / WebKit skip them. Thanks to xet7.
  • Server-side Mocha suite: fixed a startup crash and the 44 latent failures it had been hiding.
    • 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. Thanks to xet7.
  • Fix translations at Login and Register pages. Thanks to xet7.

Thanks to above GitHub users for their contributions and translators for their translations.