Area: Forums (audit p2) · Surface: POST /api/bookmark/toggle (BookmarkController@toggle) + GET /bookmarks (BookmarkController@index) · Dimension: Broken Access Control / IDOR / Information Disclosure (OWASP A01:2021) · Severity: minor
Any authenticated user can bookmark an arbitrary thread or post by guessing/iterating its integer ID — even one inside a hidden or listed_private forum or a paid forum they have not paid for — and then read its title (threads) or the first 160 characters of its body (posts) back on their /bookmarks page. This is an IDOR that turns the bookmark feature into an oracle for content in forums the user is not authorized to view. Severity is limited to title + body prefix disclosure, but it bypasses the Forum::canView gate that protects the actual thread/post pages.
Evidence
platform/src/Controllers/BookmarkController.php:41-54 — existence is checked but visibility is not: for a thread `$exists = $this->db->fetchOne('SELECT id FROM forum_threads WHERE id = :id', ['id'=>$targetId]);` (no Forum::canView). If it exists, Bookmark::toggle() stores it (line 56). The /bookmarks list then SELECTs and renders the bookmarked thread title / first 160 chars of post body: BookmarkController.php:131-161 (`CASE WHEN b.target_type='thread' THEN t.title ... THEN SUBSTRING(p.body,1,160) END AS target_title`) with no forum-visibility join/filter.
Suggested fix. In toggle(), after confirming the target exists, resolve its forum_id and enforce Forum::canView()/isPreviewOnly() (and Forum::hasPaidAccess for paid forums) before allowing the bookmark; reject with 404 otherwise. As defense in depth, also filter the /bookmarks index query by the viewer's forum visibility so previously-stored bookmarks to now-inaccessible forums don't leak their titles.
Filed by the automated tenant-app audit and adversarially evidence-verified. Status: verified. Open — not yet actioned.
Patrick Bass
@mobieus