Area: Files, photos, gallery, ansi (audit p5) · Surface: POST /photos/item/{id}/reaction (PhotoGalleryController@toggleReaction) · Dimension: security · Severity: minor
OWASP A01:2021 Broken Access Control. An authenticated user who cannot view a private (only_me/friends) album can still attach reaction rows to its photos by guessing the sequential item_id, and can spawn reaction rows against deleted or non-existent item_ids. Lower impact than the comment IDORs (reactions carry no free text) but it is the same missing-authorization pattern and shows up to the owner as interaction on private content.
Evidence
platform/src/Controllers/PhotoGalleryController.php:509-537 — toggleReaction() validates only the reaction `kind` enum, then reads/writes photo_reactions keyed on the raw item_id with no item lookup and no viewableBy check:
```php
public function toggleReaction(string $id): void
{
$user = $this->requireAuth();
$this->validateCsrf();
$kind = (string) ($_POST['kind'] ?? 'like');
if (!in_array($kind, ['like','love','laugh','wow','sad','angry'], true)) { ... }
$existing = $this->db->fetchOne('SELECT id, kind FROM photo_reactions WHERE item_id = :i AND user_id = :u', ...);
...
$this->db->execute('INSERT INTO photo_reactions (item_id, user_id, kind) VALUES (:i, :u, :k)', ['i' => (int) $id, ...]);
}
```
No PhotoAlbumItem::findById / PhotoAlbum::viewableBy, unlike tagItem()/reportItem()/listTags() in the same controller.
Suggested fix. Resolve the item + parent album and enforce PhotoAlbum::viewableBy before reading/inserting into photo_reactions, consistent with the other per-item endpoints in this controller.
Filed by the automated tenant-app audit and adversarially evidence-verified. Status: verified. Open — not yet actioned.
Patrick Bass
@mobieus