Area: mobieusAI (audit p11) · Surface: AI — mobieusAI community (legacy AiController) · Dimension: security · Severity: major
OWASP A01:2021 Broken Access Control (IDOR). An authenticated standard user can summarize forum threads they cannot view by guessing/enumerating thread IDs against /api/ai/thread-summary/{id}. The summary and extracted key points leak the private thread's actual content (the LLM reads up to 60 posts of the thread body). Per-tenant DB isolation does not help here — this is an intra-tenant private/hidden-forum leak, exactly the gap the rewritten MemberAI endpoint added a guard for.
Evidence
platform/src/Controllers/AiController.php:37-52 summarizeThread() does only requireAuth()+validateCsrf()+flag gate, then calls AiThreadSummary::forThread((int)$id). AiThreadSummary::forThread (platform/src/Services/AiThreadSummary.php:19-100) loads the thread by id and pulls posts with NO Forum::canView / forum-visibility check: line 24 `SELECT id, title, reply_count FROM forum_threads WHERE id = :id` and line 49 `SELECT u.username, p.body ... FROM forum_posts p ... WHERE p.thread_id = :tid`. Contrast with the newer MemberAIController@summarizeThread (platform/src/Controllers/MemberAIController.php:45-48) which DOES gate: `if (!\App\Models\Forum::canView((int)$thread['forum_id'], ...) || \App\Models\Forum::isPreviewOnly(...)) { json(...thread_not_found,404); }`. The route is live in the authenticated group (routes.php:1094) so any role-2 member can POST a guessed thread id and receive an AI-generated summary + key_points of a private/hidden forum thread's contents. The file's own header comment (AiController.php:21-28) calls these methods LEGACY but they remain routed.
Suggested fix. Add the same intra-tenant visibility gate the MemberAI path uses before calling AiThreadSummary::forThread: resolve forum_id for the thread and reject with 404 when !Forum::canView(forumId, userId, role) || Forum::isPreviewOnly(...). Pass the caller's id/role into AiThreadSummary::forThread (or move the check into the service). Better: retire the legacy route entirely (the header comment already says do not wire new UI to it) by removing routes.php:1094-1095 since MemberAI supersedes it.
Filed by the automated tenant-app audit and adversarially evidence-verified. Status: verified. Open — not yet actioned.
Patrick Bass
@mobieus