Forums Bug Reports Thread

Information disclosure: /tag/{slug} leaks thread titles + forum names from hidden / private / paid forums to anonymous visitors

Patrick Bass · Jun 6 · 16 · 1 Locked
[Major] [High Priority] [Bug Fixed] [Always Reproduces]
🚀 OP Jun 6, 2026 8:51pm

Area: Forums (re-run) (audit p2r) · Surface: GET /tag/{slug} (TagController@show → Tag::recentThreads) · Dimension: Security (OWASP) · Severity: major

Any thread that lives in a hidden, private, or paid (access_rule) forum but carries a public tag is enumerated on the public tag landing page, including its title, its slug (so the canonical /thread/{slug} URL is disclosed), and the restricted forum's name — to logged-out users. This defeats the confidentiality of hidden/paid forums for any tagged thread. OWASP A01:2021 (Broken Access Control — sensitive data exposure via missing object-level authorization in a list query). Distinct from the already-filed photo-album leak (different surface and model).

Evidence

TagController::show (platform/src/Controllers/TagController.php:26-75) has NO requireAuth and no forum-visibility filtering; it calls `Tag::recentThreads((int) $tag['id'], $page, $limit, $sort)`. Tag::recentThreads (platform/src/Models/Tag.php:264-313) selects `ft.title, ft.slug, ... f.name AS forum_name, f.slug AS forum_slug FROM thread_tags tt JOIN forum_threads ft ON ft.id = tt.thread_id JOIN forums f ON f.id = ft.forum_id WHERE tt.tag_id = :t` — joined with NO predicate on f.visibility or f.access_rule and no canView call. Forum::canView (platform/src/Models/Forum.php:330-357) shows 'hidden' forums and access_rule (paid/boosters_only) forums are normally invisible to non-members. The template renders them to everyone: templates/tags/show.php:77 `<a href="/thread/<?= $e($t['slug']) ?>"...><?= $e($t['title']) ?></a>` and :80 the forum name. Output is escaped so no XSS, but titles/slugs/forum names of restricted forums are exposed.

Suggested fix. Add forum-visibility filtering to Tag::recentThreads (and the matching count query): pass the viewer id/role and exclude rows the viewer cannot see — e.g. only include forums where visibility='public' AND access_rule IS NULL for anonymous/non-member viewers, or post-filter each row through Forum::canView. Apply the same fix to any RSS/feed path that reuses recentThreads.

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


Patrick Bass
@mobieus

🚀 Jun 7, 2026 5:25am

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

Added forum-visibility filtering to Tag::recentThreads. New backward-compatible params $viewerId/$viewerRole (default anonymous). SuperAdmins (role>=5) see all; others get a SQL prefilter (joined forums now also in the COUNT query) excluding soft-deleted and hidden forums, with non-mod viewers further limited to public/listed_private, then every returned row is post-filtered through Forum::canView to honor access_rule, paid access, and private-forum membership. Defaults to the safest (anonymous) view until TagController passes the real viewer.

Status: fixed. Thread closed and locked.


Patrick Bass
@mobieus

Log in or register to reply to this thread.