CakePHP's Model::set() takes the record id from a primary key in the
data passed to save(). edit() authorized the id in the URL and then saved
the request body, so Zone[Id]=<other> in the body wrote to that other
zone, past the per-monitor check just added. add() could likewise update
an existing row instead of creating one.
Add AppController::pinRequestId(), which drops the primary key from the
request data and sets the model id, and use it in these edits (pinned to
the URL id) and adds (cleared). Frames and EventData edit() never set the
model id at all, so a body without an Id inserted a new row rather than
updating; pinning fixes that too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FramesController::add() and EventDataController::add() saved request data
behind only the controllers' Events != None gate, so an Events=View user
could create rows for any event, including one on a monitor they are
denied. edit() checked the existing row but not the event or monitor the
request moved it to.
add() now requires Events=Edit and edit on the event named by EventId,
which covers that event's monitor. For EventData a supplied MonitorId must
also be viewable. edit() applies the same check to any EventId/MonitorId
in the request. Load includes/Event.php explicitly rather than relying on
the model association to have pulled it in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EventDataController and TagsController both authorized only with the
account-wide Events permission in beforeFilter() (Events != None) and
never applied the per-monitor ACL that EventsController resolves via
unviewableMonitorIds()/viewableMonitorIds(). A monitor-restricted account
could therefore read data belonging to cameras it is explicitly denied.
EventDataController::index() and ::view() now filter on Event_Data.MonitorId
(the table carries its own MonitorId, so the restriction applies directly,
mirroring EventsController's Event.MonitorId filter). ::view() returns 403
when the row exists but is outside the caller's allowed monitors, matching
EventsController. ::edit() and ::delete() now go through requireEventDataEdit(),
which requires Events=Edit plus the per-monitor ACL on the target row, so a
restricted or view-only account cannot mutate or delete another monitor's
event data by Id. The pre-existing view/edit reads also stop hardcoding a
"Event_Data." alias prefix that does not match the model alias, using
$this->EventData->alias instead.
TagsController exposes per-monitor data only where a tag is joined to its
events: index() filtered by Events.Id (returns Events_Tags.EventId) and
associations() (contains each tag's Events). Both now restrict the joined
Events to viewable monitors when the caller is monitor-restricted. The plain
tag list is a global label vocabulary shared across monitors and is left
unrestricted so restricted users can still tag their own events.
This is the same defect class as GHSA-mg2g-jmfc-3w8g (FramesController),
reported here as GHSA-rgwp-wvjw-6925 for EventDataController; the Tags
controller was found to share the shape during the audit the report
suggested.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>