java-topology/whitepaper/outreach/wekan-0002.md
russell@unturf.com 6784cdf1cf feat: add 39 outreach docs (batches 6-8)
Batch 6 (9): dolibarr, jitsi-videobridge, zed, tryton, suricata,
  strawberry, zulip, zesarux, zephyr
Batch 7 (15): xonotic (4), xash3d (3), xenia, xtuple, zabbix (2),
  zathura, zebra, yabause, zephyr-0001
Batch 8 (15): woodpecker (2), wine (4), widelands (3), wesnoth (3),
  wekan (3)

Mix of CWE-407 and CWE-312.
2026-04-14 17:06:28 -04:00

3 KiB
Raw Permalink Blame History

WeKan — CWE-407 Disclosure Brief (wekan-0002)

2026-04-13 · Patch available — awaiting upstream merge

Finding

One O(n²) defect in WeKan's card member filtering. The member and watcher permission filter in models/cards.js uses Array.includes() for membership checks and a nested Array.filter().includes() for change detection, producing O(M²) where M = members per card. Fires on every card access control check.

The Defect

wekan-0002 (PATCHED — MEDIUM): models/cards.js:2155

// Card member/watcher permission filter — fires on every card operation:
const filteredMembers = currentMembers.filter(
    memberId => allowedMemberIds.includes(memberId));  // O(A) per member
if (currentMembers.filter(
    x => !filteredMembers.includes(x)).length > 0) {   // O(F) per member
    mutatedFields.members = filteredMembers;
}

const filteredWatchers = currentWatchers.filter(
    watcherId => allowedMemberIds.includes(watcherId)); // O(A) per watcher
if (currentWatchers.filter(
    x => !filteredWatchers.includes(x)).length > 0) {   // O(F) per watcher
    mutatedFields.watchers = filteredWatchers;
}

Four separate linear scans nest together: filter with includes for filtering, then filter with includes for change detection. Total: O(M × A + M × F) where A = allowed IDs and F = filtered result size.

Complexity Proof

At M=200 members, A=200 allowed:

  • Defective: 200 × 200 filter + 200 × 200 change detection = ~80,000 ops (×2 for watchers)
  • Fixed: 200 Set builds + 200 Set.has() + length comparison = ~600 ops
  • ~130× op reduction.

Impact

WeKan enterprise deployments use boards with many members. Every card operation (view, edit, move, comment) runs the permission filter. Organizations with hundreds of board members see the quadratic cost on every card interaction.

The Fix

Build a Set from allowed IDs for O(1) membership, compare lengths for change detection:

// Before
currentMembers.filter(memberId => allowedMemberIds.includes(memberId))
currentMembers.filter(x => !filteredMembers.includes(x)).length > 0

// After
// CWE-407 fix: Set for O(1) membership, length comparison for change detection.
const allowedSet = new Set(allowedMemberIds);
const filteredMembers = currentMembers.filter(memberId => allowedSet.has(memberId));
if (filteredMembers.length !== currentMembers.length) { ... }

Patch

Fix available: defects/wekan-0002/patch/wekan-0002.patch

Single-file patch in models/cards.js. Replaces includes with Set.has(), replaces nested filter change detection with length comparison. ~130× speedup at M=200.

What We Ask

A patch is ready for review.

  1. Confirm receipt and assign a GitHub issue reference (wekan/wekan).
  2. Assess severity — fires on every card member/watcher permission check.
  3. Coordinate a disclosure date — we target 90 days from first contact.
  4. We will credit the WeKan team in the public disclosure. Preferred acknowledgment format welcome.

Contact: see cover email. This brief is confidential until coordinated disclosure.