Hall of Fame image from https://openclipart.org/detail/120343/trophy
Back to Hall of Fame Contents Back to Wekan Website

Contents / SignupBleed

CVE Vulnerability name Date Responsible Security Disclosure by Vulnerabilities


SignupBleed

2026-08-16 AhmedLukman and xet7

Found while reviewing a pull request about the opposite symptom.
  • SignupBleed — with registration turned OFF in the Admin Panel, POST /users/register created accounts anyway and handed back a login token, because its guard read a Meteor option WeKan never sets (CWE-862 Missing Authorization)
  • The Admin Panel switch closed the sign-up FORM and nothing else. A closed instance answered the REST endpoint exactly as an open one did, so anybody who could reach the port could make an account on it
  • The guard read Accounts._options.forbidClientAccountCreation. WeKan's only Accounts.config() call sets loginExpirationInDays and nothing else; forbidClientAccountCreation: disableRegistration is passed to AccountsTemplates.configure(), which is the useraccounts package's own options object; and that value is assigned from an async Meteor.call('isDisableRegistration') callback that fires after configure() has already run. Three near-misses, and the condition was always false
  • No credentials were exposed and no existing data was reachable: the fault is that an instance whose administrator had closed registration kept accepting new accounts. What those accounts can then see is whatever a new account can see on that deployment. Affects deployments with WITH_API=true
  • The endpoint now reads getCurrentSetting().disableRegistration, the same field the sign-up form's own Meteor method reads, so the form and the API cannot disagree. A refusal is recorded and shows in Admin Panel / Problems — every call that reaches it is an attempt, because the administrator has turned registration off
  • Fixed at upcoming WeKan release


Details

SignupBleed — the switch that closed the form and nothing else (CWE-862)

WeKan's Admin Panel has a Registration setting. Turning it off is how an administrator says that nobody else may join this instance. It closed the sign-up form. It did not close the REST endpoint behind it:

POST /users/register
    

The endpoint did have a guard, and the guard looked exactly right:

// server/apiAuthRoutes.js — before
if (Accounts._options.forbidClientAccountCreation) {
  sendJsonResult(res, { code: 403 });
  return;
}
    

Nothing in WeKan ever sets that option. Three separate things each nearly set it, and none of them does:

So the condition was always false, and the endpoint had never refused anyone. It read as though it worked, which is why it lasted: the option is named exactly for the job it was not doing.

The fix reads the setting from where the setting lives — the same field the sign-up form's own Meteor method reads, so the form and the API can no longer disagree about whether the door is open:

// server/apiAuthRoutes.js — after
const setting = await ReactiveCache.getCurrentSetting();
if (setting?.disableRegistration === true) {
  require('/server/lib/securityLog').record({
    key: 'authz.register', action: 'blocked', source: 'POST /users/register',
    detail: 'refused account creation while registration is disabled',
  });
  sendJsonResult(res, { code: 403 });
  return;
}
    

The optional chain matters: an instance with no Settings document yet still allows registration, rather than locking itself out on first run. The refusal is recorded because every call that reaches it is an attempt — once the administrator has turned registration off there is no legitimate caller, so the log cannot drown Admin Panel / Problems in ordinary traffic.

tests/restRegisterRespectsSetting.test.cjs pins nine facts, four of them negative, on an endpoint that had no tests at all: 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.

How it was found

Pull request #6598 by AhmedLukman changed this guard to read WeKan's setting. It was filed against issue #4774, a 403 Forbidden from this endpoint that nobody had been able to reproduce — and which this line cannot produce, because it never returned 403 at all. Looking into why turned up the opposite fault, and a more serious one. The pull request was closed and the fix written with a security log entry and the tests; the finding is the reporter's.



Back to Hall of Fame Contents Back to Wekan Website