Forums Bug Reports Thread

Photo albums in private/hidden forums leak to any logged-in user — forum visibility (Forum::canView) is never enforced on album surfaces

Patrick Bass · Jun 6 · 18 · 1 Locked
[Major] [High Priority] [Bug Fixed] [Always Reproduces]
🚀 OP Jun 6, 2026 5:27pm

Area: Forums (audit p2) · Surface: GET /forums/{slug}/photos, GET /forums/category/{slug}/photos, GET /photos/album/{id}, GET /photos/serve/{id} (PhotoGalleryController) · Dimension: Broken Access Control / IDOR (OWASP A01:2021) · Severity: major

Photos and videos uploaded to a forum that is hidden or listed_private (or under a category whose min_role_view exceeds the viewer's role) are exposed to any authenticated user. The /forums/{slug}/photos and /forums/category/{slug}/photos listing pages render album titles, descriptions and cover thumbnails without a visibility gate; /photos/album/{id} renders the full album and /photos/serve/{id} streams the actual image/video bytes, both gated only by viewableBy() which treats the default 'members' privacy as 'any logged-in user' rather than 'member of THIS community'. A non-member can enumerate album IDs (or follow the leaked list links) and view private-forum media. The rest of the forum subsystem consistently enforces Forum::canView; these photo surfaces are the gap.

Evidence

platform/src/Controllers/PhotoGalleryController.php:618-655 forumAlbums() and :540-566 communityAlbums() look up the forum/category by slug and render PhotoAlbum::byCommunity() with NO Forum::canView() / category min_role_view check (unlike every other forum surface, e.g. ForumController::showForum:157 and ForumInteractionController::vote:66 which all call Forum::canView). The album-list template renders every album's title + cover image: platform/templates/photos/community-albums.php:100-104 (`<a href="/photos/album/<id>"> ... <img src="<?= $e($coverSrc) ?>">`). The album page and the image bytes gate ONLY on PhotoAlbum::viewableBy(): showAlbum():78 and serve():1187. viewableBy() (platform/src/Models/PhotoAlbum.php:189-210) returns TRUE for privacy='members' for ANY logged-in viewer (line 194 `if ($privacy === 'members') return true;`) and 'members' is the default privacy (createForumAlbum:682 `$privacy = (string)($_POST['privacy'] ?? 'members')`). It never consults forum membership or Forum::canView/isPreviewOnly.

Suggested fix. Before rendering community/forum album lists, album pages, and serving items, resolve the owning community: if community_id maps to a forum, require Forum::canView(forumId, viewerId, role) && !isPreviewOnly(); if it maps to a forum_category, require category min_role_view <= role. Make PhotoAlbum::viewableBy() community-aware: for community albums, 'members' must mean 'passes the owning forum/category visibility + membership check', not 'any authenticated user'. Also route cover images through /photos/serve (with the gate) instead of emitting the raw storage path in the template.

Filed by the automated tenant-app audit and adversarially evidence-verified. Status: verified. Open — not yet actioned.


Patrick Bass
@mobieus

🚀 Jun 7, 2026 4:59am

Resolved — fixed and deployed. Commit faa14fc59739, shipped dev-first then to all tenants on 2026-06-06.

Added access gates on the two unprotected community photo list surfaces: communityAlbums() now 403s when viewer role < forum_categories.min_role_view, and forumAlbums() now 403s unless Forum::canView($forumId,$viewerId,$role). The other two cited surfaces (showAlbum /photos/album/{id} and serve /photos/serve/{id}) already had correct PhotoAlbum::viewableBy gates, so they were left unchanged.

Status: fixed. Thread closed and locked.


Patrick Bass
@mobieus

Log in or register to reply to this thread.