Wekan
Open source kanban board application built with Meteor
Alternative to: trello
v10.96
2026-08-16Binaries in these bundles
Each bundle carries a Node.js, a FerretDB and the MongoDB Database Tools. Which source has a given CPU varies from release to release - nodejs.org builds some architectures, unofficial-builds others, and the wekan/node-patches build the ones neither of them does - and not every source publishes a checksum. This is what went into this release, and which downloads were checked against a published SHA256.
| Bundle | Binary | From | Version | Checked | SHA256 |
|---|---|---|---|---|---|
| amd64 | FerretDB | wekan/FerretDB | v1.53.0 | verified | eae1f0a8f73bfc97… |
| amd64 | Node.js | nodejs.org | v24.19.0 | verified | 14b342e71204f811… |
| arm64 | FerretDB | wekan/FerretDB | v1.53.0 | verified | bdc50caee3ac2849… |
| arm64 | Node.js | nodejs.org | v24.19.0 | verified | 01443c1e1a29e531… |
| armhf | FerretDB | wekan/FerretDB | v1.53.0 | verified | f7f050207e656d65… |
| armhf | Node.js | wekan/node-patches | v24.19.0 | verified | b55350f3071b765a… |
| armv6 | FerretDB | wekan/FerretDB | v1.53.0 | verified | 34f5f553dcd2bff3… |
| armv6 | Node.js | wekan/node-patches | v24.19.0 | verified | 128ded0cda638c1f… |
| armv7 | FerretDB | wekan/FerretDB | v1.53.0 | verified | f7f050207e656d65… |
| armv7 | Node.js | wekan/node-patches | v24.19.0 | verified | 8dbe0a9aa8550ad5… |
| i386 | FerretDB | wekan/FerretDB | v1.53.0 | verified | 515b1189b4c7d58f… |
| i386 | Node.js | wekan/node-patches | v24.19.0 | verified | 3b0b3bbfe27daf58… |
| mac-arm64 | FerretDB | wekan/FerretDB | v1.53.0 | verified | cb14ffe93e285903… |
| mac-arm64 | Node.js | nodejs.org | v24.19.0 | verified | 3f1cf157479c1480… |
| mac-x64 | FerretDB | wekan/FerretDB | v1.53.0 | verified | d97dfa9afa60aa05… |
| mac-x64 | Node.js | nodejs.org | v24.19.0 | verified | d35e95230f46f6f0… |
| ppc64le | FerretDB | wekan/FerretDB | v1.53.0 | verified | 481d36a80ec73673… |
| ppc64le | Node.js | nodejs.org | v24.19.0 | verified | c510c6ce12f07010… |
| riscv64 | FerretDB | wekan/FerretDB | v1.53.0 | verified | 4b43f86eb0f3fa0b… |
| riscv64 | Node.js | unofficial-builds.nodejs.org | v24.19.0 | verified | cd1f14af28121480… |
| s390x | FerretDB | wekan/FerretDB | v1.53.0 | verified | fe74e03894b84897… |
| s390x | Node.js | nodejs.org | v24.19.0 | verified | a4792e65962ffa0a… |
| win-arm64 | FerretDB | wekan/FerretDB | v1.53.0 | verified | f1186c310a4dd144… |
| win-arm64 | Node.js | nodejs.org | v24.19.0 | verified | 8502f4a50b458d4c… |
| win64 | FerretDB | wekan/FerretDB | v1.53.0 | verified | c6dad7bc8ad56f47… |
| win64 | Node.js | nodejs.org | v24.19.0 | verified | 57f71ab3652e797d… |
A row saying no checksum published is not a failed check - it is a source that publishes nothing to check against. Those are the ones worth fixing at the source.
v10.96 2026-08-16 WeKan ® release
In short: one CRITICAL fix. With registration turned OFF in the Admin
Panel, POST /users/register created accounts anyway — for anybody who asked,
on an instance whose administrator had decided nobody else may join. The guard
that was supposed to stop it read a Meteor option WeKan never sets, so it was
always false and the endpoint had never refused anyone. It was found while
reviewing a pull request about the opposite symptom. Then the release
workflow: pressing Cancel did not stop a run, so docker went on building
an image for a release being abandoned, and the Sandstorm .spk gets under
its 1 GiB limit at last, now that its size report says what filled the gigabyte.
What that measurement found ended up changing every platform, not just
Sandstorm: uws is not reliable enough yet, so every default is now
sockjs, and no bundle carries uWebSockets.js (121M), the legacy client
(81M) or source maps (152M) — around 354M a bundle, none of it on any
loading path. Below that: two AWS SDK updates for the S3 attachment path.
| Platform | Binary | From | Version | SHA256 |
|---|---|---|---|---|
| amd64 | Node.js | nodejs.org | v24.19.0 | 14b342e71204f811bde6153be8e04b62aef63c236fef92b55f9c83154b409647 |
| amd64 | FerretDB | wekan/FerretDB | v1.53.0 | eae1f0a8f73bfc979738bfff7284d40fd1bc55de2cc56514721fc155c3624f7d |
| arm64 | Node.js | nodejs.org | v24.19.0 | 01443c1e1a29e531ccad5a46fefa6df490d2189c49f7955904aecdbb0fe86fdc |
| arm64 | FerretDB | wekan/FerretDB | v1.53.0 | bdc50caee3ac28495b42d2130b94a042a9dd6d3a38f732cac02b648f36c891da |
| mac-arm64 | Node.js | nodejs.org | v24.19.0 | 3f1cf157479c1480352083105e13faf9d008ede98e7e157746b6df940d197b94 |
| mac-arm64 | FerretDB | wekan/FerretDB | v1.53.0 | cb14ffe93e285903e5a8a9c1821687ddb5b8a979a11c584bf4af534b272c6d3e |
| mac-x64 | Node.js | nodejs.org | v24.19.0 | d35e95230f46f6f0751df497c56622c6735e05d5e1fb1630996a005b9d328fe4 |
| mac-x64 | FerretDB | wekan/FerretDB | v1.53.0 | d97dfa9afa60aa05f25384327de82efe7b71d958ed24c1f66618284294a65cd3 |
This release fixes the following CRITICAL SECURITY ISSUE of SignupBleed:
Account creation - who may make an account, and who decides.
SignupBleed: registration disabled, and the REST API created accounts anyway. Thanks to AhmedLukman and xet7.
Turning Registration off in the Admin Panel closes the sign-up form. It did
not close POST /users/register, which went on creating accounts and handing
back login tokens to anyone who asked. A closed instance was open.
The endpoint did have a guard. It asked
Accounts._options.forbidClientAccountCreation — and nothing in WeKan ever
sets that:
- the only
Accounts.config()call, inserver/accounts-common.js, setsloginExpirationInDaysand nothing else; forbidClientAccountCreation: disableRegistrationinconfig/accounts.jsis passed toAccountsTemplates.configure(), which is the useraccounts package’s own options object, not Meteor’sAccounts;- and that
disableRegistrationis only assigned inside an asyncMeteor.call('isDisableRegistration', …)callback that fires AFTERconfigure()has already run — something the file’s own comment records, for a different reason, a few lines above.
Three near-misses, and the condition was always falsy. The guard read as though it worked, which is why it lasted: the name says exactly what it ought to do.
It reads the setting from where the setting lives now - the same
getCurrentSetting().disableRegistration === true that the
isDisableRegistration Meteor method behind the sign-up form uses, so the form
and the API can no longer disagree about whether the door is open. An instance
with no Settings document yet still allows registration rather than locking
itself out.
Every call that reaches the refusal is recorded and shows in Admin Panel /
Problems under authz.register. That is safe to log without drowning the page
precisely because there is no legitimate caller: the administrator has turned
registration off.
How it was found. #6598 by AhmedLukman changed this guard to read WeKan’s setting, filed against #4774 — a 403 Forbidden from this endpoint that nobody had been able to reproduce, and which this line cannot produce, since it never returned 403 at all. The pull request was closed and the fix written here with a security log entry and the tests the route had never had; the finding is the reporter’s.
Nine tests, four of them negative, on an endpoint that had none: that the setting is read where the Meteor method reads it, that the dead option is not consulted again AND is still set nowhere — so a later change cannot quietly reintroduce two sources of truth that disagree — that an enabled instance still creates the user and answers with its token, and that a missing Settings document does not refuse everybody.
and updates the following dependencies:
- @aws-sdk/client-s3 3.1108.0 → 3.1109.0 — the S3 client WeKan stores attachments through when S3 storage is configured.
- @aws-sdk/lib-storage 3.1104.0 → 3.1109.0 — the multipart upload helper beside it, which is what actually streams a large attachment to S3.
Thanks to dependabot.
and fixes the following build failures:
The release workflow - what a release builds, and who can stop it.
Cancelling a release run stops it, and a stuck job no longer needs a human. Thanks to xet7.
Pressing Cancel on a release-all run did not stop the docker job. It kept
going for another twenty minutes and would have pushed a multi-arch image for a
release that was being abandoned.
The cause is always(). It is true while a run is CANCELLING, which is exactly
why it was chosen in v10.80: a build-mac-x64 queued on a runner label GitHub
had retired sat for two hours, was cancelled by hand, and that cancellation
SKIPPED charts, ucs and nextcloud — so the Helm chart for an already
published WeKan was never pushed, and no error anywhere said why. always()
fixed that by ignoring cancellation entirely, and ignoring cancellation is also
what disabled the Cancel button on a job that takes hours.
!cancelled() keeps the half that matters — do not skip me because a SIBLING
failed — and drops the half that overrides a person. Nine job-level conditions
change, across release-all.yml, release-all-missing.yml, Flatpak.yml and
AppImage.yml. The 22 step-level if: always() reporting steps do not: those
correctly print CANCELLED.
That alone would reopen v10.80, so the other half of the fix is that no job
needs a hand cancellation any more. The eight jobs that had no timeout-minutes
now have one, and every job in the release workflow is bounded. A stuck job
FAILS on its own, and a failure satisfies !cancelled() the same way always()
let it through — Cancel is left meaning only what a person meant by it.
The Sandstorm .spk gets under 1 GiB, by dropping what a grain cannot run. Thanks to xet7.
v10.93, v10.94 and v10.95 all failed to pack with App exceeds uncompressed size
limit of 1 GiB. v10.95 was the first run whose size report worked, and it named
the gigabyte: .meteor-spk/bundle 852M plus .meteor-spk/deps 336M, so 1188M
against a 1024M ceiling. It also answered the question that report was written
to ask — a second meteor-spk pack REUSES the bundle rather than rebuilding it,
since the pruned tree stayed pruned across the retry (852M to 833M). So trimming
between the two attempts works. 19M was simply never going to close a 165M gap.
Measuring the bundle rather than guessing at it turned up two passengers that are large, unreachable at runtime, and safe to drop:
- uWebSockets.js ships twenty prebuilt binaries — Linux/macOS/Windows times
x64/arm64 times four Node ABIs, 121M — and its loader is one line:
require('./uws_' + process.platform + '_' + process.arch + '_' + process.versions.modules + '.node'). A machine is one platform running one Node, so the other sixteen files can never be opened by it. Keeping every ABI of the target platform and CPU, so a Node major bump still finds its binary, frees ~93M. The entry below drops the remaining 28M as well. - Source maps — 4766 files, 188M, over a fifth of the bundle. They exist for
a debugger attached to the process. A packed app has none, and a missing
.mapdegrades a stack trace at worst.
releases/bundle-trim.mjs does both, measured at 281 MiB on a real bundle, and
the Sandstorm leg runs it beside the existing prune before the retry pack.
An architecture with NO uWebSockets.js prebuild at all — ppc64le, s390x,
riscv64, where ddp-server falls back to sockjs — is left completely alone, since
deleting the other platforms’ files there would free nothing that matters and
could only break the fallback. tests/bundleTrim.test.cjs pins that, the kept
ABIs, that a directory merely ending in .map is not a source map, and that the
trim runs before the retry pack rather than after it.
The Sandstorm .spk ships no uWebSockets.js at all, because a grain talks sockjs. Thanks to xet7.
Trimming uWebSockets.js down to the prebuilds a grain’s own platform can open freed 93M of its 121M. All of it can go, because a grain loads NONE of it.
The uws transport is OPTIONAL in Meteor 3. ddp-server’s transports/index.js
resolves the transport from Meteor.settings.packages['ddp-server'].transport,
then DDP_TRANSPORT, then DISABLE_SOCKJS, and DEFAULTS to sockjs — and
Npm.require('uWebSockets.js') sits inside the uws transport’s setup(), which
runs only for the transport that was actually chosen. sandstorm-pkgdef.capnp
sets none of those, and its environ is, by its own comment, the app’s ENTIRE
environment. So a grain has been running sockjs all along while carrying 121M of
a module it never required.
The pkgdef now PINS DDP_TRANSPORT=sockjs, because the removal should rest on a
stated fact rather than on an upstream default staying put, and
bundle-trim.mjs takes --transport sockjs, which removes the module whole
instead of thinning its prebuilds. 120 MiB measured, 27 MiB more than the
platform trim: 1188M becomes ~860M, with over 160M of headroom against the
1 GiB limit.
It is a flag rather than the default because WeKan asks for uws nearly
everywhere else — docker-compose, start-wekan.sh, build.sh — and those
bundles keep the module, trimmed to the prebuilds their own platform can open.
tests/bundleTrim.test.cjs ties the grain’s pinned transport to the flag the
.spk is trimmed with. If those two ever disagreed, the grain would require a
module that was left out and fail to boot, which is the one way this can go
wrong.
and changes what every platform ships:
Bundles and images - what a build carries, and what it no longer does.
Every platform talks sockjs, and no bundle carries uws or the legacy client. Thanks to xet7.
uws is not reliable enough yet to be what a default points at, so nothing WeKan ships selects it any more, and nothing carries the module. Three halves, and each is useless or harmful without the others.
The defaults. Every docker-compose*.yml, start-wekan.sh,
start-wekan.bat, build.sh, build.bat, the image’s own ENV block, the
snap config and the Sandstorm grain now say sockjs. The commented-out
#- DDP_TRANSPORT=uws alternatives beside them are replaced by a note saying
why there is no alternative, rather than advertising a value that no longer
works.
The bundles. Every place a built bundle is post-processed — the amd64
build, each per-arch rebuild, the repack container,
releases/install-node-for-arch.sh and the Dockerfile — now runs
bundle-trim.mjs beside the prune it already ran, dropping two things:
-
uWebSockets.js, 121 MiB, for the reason in the entry above: a sockjs server never requires the module at all.
-
programs/web.browser.legacy, a whole second copy of the client built for browsers without modern JS. 81 MiB measured. Meteor supports running with architectures excluded and says so in webapp’scategorizeRequest(): “If our preferred arch is not available, it’s better to use another client arch that is available than to guarantee the site won’t work”. An old browser is servedweb.browser; the 404 branch below that is reached only when NO arch matches, which cannot happen whileweb.browseris there, and autoupdate iterates the programs that actually loaded rather than a fixed list.The files are only half of it. The arch is NAMED in
programs/server/config.jsonandstar.json, andboot.jsbuilds a dynamic-import root for every name in the first — so the name is removed with the files. Those manifests are mode 444 as Meteor writes them, so they are made writable, rewritten, and set back: failing there would leave the one state that actually breaks a server, files gone and manifests still naming the arch.
The upgrade. An existing docker-compose.yml that says
DDP_TRANSPORT=uws would ask the new image for a module it does not have and
crash-loop on the require. So the Docker entrypoint and the bundle’s own
start-wekan.sh coerce uws back to sockjs before starting anything, and
print why — a setting silently ignored is worse than one that fails.
tests/sockjsEverywhere.test.cjs pins the three halves together: a default
without the coercion is an upgrade trap, a coercion without the trim is dead
weight, and a trim without both is a crash.
Source maps go from every platform, not only the Sandstorm .spk. Thanks to xet7.
The .zip bundles and the Docker image were trimmed with --keep-maps, so only
Sandstorm — which had a 1 GiB ceiling to get under — dropped them. Every
platform drops them now: 152 MiB per bundle, measured. That is 188 MiB
across 4766 files, less the 36 MiB inside web.browser.legacy that the entry
above already takes with it.
Nothing on any loading path reads one. A source map translates a position in
built code back to the source that produced it — this bundle’s server side is
one 117 MiB programs/server/app/app.js, and its 58 MiB app.js.map is what
turns app.js:1284531 into a file and a line. The two things that read it are
browser devtools, which fetch the .map only while they are open, and Node
stack traces through source-map-support. A released bundle has neither
attached to it.
The //# sourceMappingURL comments stay behind, and are comments: a missing
target means devtools show compiled positions and a server stack trace prints
bundle offsets. Debugging a production crash goes back to reproducing it against
a development build, which is where the maps still are. The guard pins that no
call site keeps them, so one platform cannot quietly drift back to carrying
them.
Thanks to above GitHub users for their contributions and translators for their translations.