Area: Files, photos, gallery, ansi (audit p5) · Surface: GET /photos/item/{id}/comments (PhotoGalleryController@listComments) · Dimension: security · Severity: major
OWASP A01:2021 Broken Access Control (IDOR). The comments-listing endpoint never resolves the parent album or checks viewability, so a logged-in user can enumerate photo item IDs and harvest the full comment thread (text, author usernames, avatars) on any photo on the tenant — including photos in albums the owner restricted to 'only me' or 'friends'. This leaks private social content and an inventory of who interacted with private photos.
Evidence
platform/src/Controllers/PhotoGalleryController.php:734-746 — listComments() runs the SQL directly with no album lookup and no permission check:
```php
public function listComments(string $id): void
{
$rows = $this->db->fetchAll(
'SELECT c.id, c.body, c.created_at, u.username, u.profile_photo
FROM photo_comments c
JOIN users u ON u.id = c.user_id
WHERE c.item_id = :i AND c.is_deleted = 0
ORDER BY c.created_at ASC
LIMIT 50',
['i' => (int) $id]
);
$this->json(['ok' => true, 'comments' => $rows]);
}
```
Contrast with the sibling listTags() at PhotoGalleryController.php:918-936, which loads the item + album and calls `PhotoAlbum::viewableBy($album, $viewerId)` before returning. viewableBy (src/Models/PhotoAlbum.php:189) enforces only_me=owner-only and friends=friends-only. Route is auth-gated (routes.php:1045, inside the AuthMiddleware group) but ANY logged-in user can iterate the sequential integer item_id and read comment bodies + commenter usernames + profile_photo on photos in private (only_me / friends) albums.
Suggested fix. Mirror listTags(): load the item via PhotoAlbumItem::findById, load the album via PhotoAlbum::findById, and return 403/404 unless PhotoAlbum::viewableBy($album, $viewerId) is true before running the comments query.
Filed by the automated tenant-app audit and adversarially evidence-verified. Status: verified. Open — not yet actioned.
Patrick Bass
@mobieus