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

Contents / StaleBleed

CVE Vulnerability name Date Responsible Security Disclosure by Vulnerabilities
GHSA-r8r3-23vr-8jh6

StaleBleed

2026-08-15 ybsun0215 and xet7

Coordinated disclosure via GitHub Security Advisory GHSA-r8r3-23vr-8jh6.
  • StaleBleed — the board listing matched 'members.userId' with a dotted path, which ignores the membership's isActive flag, so a removed member kept seeing the board's id and title indefinitely (CWE-863 Incorrect Authorization)
  • Removing a board member does not delete their entry: it sets isActive: false and isAdmin: false and keeps it. A query that matches the entry without reading that flag therefore matches revoked members exactly as it matches current ones
  • Reading the board itself was already refused, so the exposure is the board's id and its title — but the id is the part that matters, because it is what every other endpoint in the API is addressed by, and the title is often the thing a private board is private about
  • This is the same class as GHSA-gwc4-fw7p-gw58 (org, team and domain shares, fixed in 10.74), whose fix introduced one builder for board visibility with the header note "A share entry counts only while it is active, everywhere". This listing endpoint predates that consolidation and was never converted
  • It uses $elemMatch now, so the userId and the active flag have to be true of the SAME entry — which is what a revoke means everywhere else in WeKan
  • Fixed at upcoming WeKan release


Details

StaleBleed — a revoke the listing did not honour (CWE-863)

A user can list the boards they are a member of:

GET /api/users/{userId}/boards
    

The selector matched the membership array with a dotted path:

// server/models/boards.js — before
const boards = await ReactiveCache.getBoards(
  {
    archived: false,
    'members.userId': paramUserId,   // ignores members.$.isActive
  },
  ...
    

A dotted match asks "does any entry have this userId?". It cannot ask "does any entry have this userId AND is active", because in Mongo those two conditions on a dotted path may be satisfied by DIFFERENT entries of the array. That is what $elemMatch is for, and it is what the rest of WeKan uses.

The fix:

// server/models/boards.js — after
members: { $elemMatch: { userId: paramUserId, isActive: true } },
    

tests/restApiIdorBatch.test.cjs pins the $elemMatch form and that the dotted match does not return.



Back to Hall of Fame Contents Back to Wekan Website