From ffed77e4d3142de27af22d32e677c0983003b952 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 28 Sep 2026 19:38:59 -0400 Subject: [PATCH] fix: pin the record id in Zones, Frames and EventData API writes refs GHSA-f8h6-62c9-x6qr GHSA-993c-fc6p-hpxg 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]= 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 --- web/api/app/Controller/AppController.php | 13 +++++++++++++ web/api/app/Controller/EventDataController.php | 2 ++ web/api/app/Controller/FramesController.php | 2 ++ web/api/app/Controller/ZonesController.php | 2 ++ 4 files changed, 19 insertions(+) diff --git a/web/api/app/Controller/AppController.php b/web/api/app/Controller/AppController.php index 7e0334e86..fabed6262 100644 --- a/web/api/app/Controller/AppController.php +++ b/web/api/app/Controller/AppController.php @@ -192,4 +192,17 @@ class AppController extends Controller { } } # end function beforeFilter() + + # Model::save() takes the record id from a primary key in the data it is given, so an + # edit authorized for the id in the URL could otherwise write to whichever id the request + # body names. Drop any primary key from the request data and pin the model to $id. + protected function pinRequestId($model, $id) { + $alias = $model->alias; + $key = $model->primaryKey; + if (isset($this->request->data[$alias]) and is_array($this->request->data[$alias])) { + unset($this->request->data[$alias][$key]); + } + unset($this->request->data[$key]); + $model->id = $id; + } } diff --git a/web/api/app/Controller/EventDataController.php b/web/api/app/Controller/EventDataController.php index 2367b0525..6dfe0e7e3 100644 --- a/web/api/app/Controller/EventDataController.php +++ b/web/api/app/Controller/EventDataController.php @@ -152,6 +152,7 @@ class EventDataController extends AppController { throw new UnauthorizedException(__('Insufficient Privileges')); } $this->requireRequestEventDataEdit(true); + $this->pinRequestId($this->EventData, null); $this->EventData->create(); if ($this->EventData->save($this->request->data)) { } @@ -173,6 +174,7 @@ class EventDataController extends AppController { } $this->requireEventDataEdit($id); if ($this->request->is(array('post', 'put'))) { + $this->pinRequestId($this->EventData, $id); $this->requireRequestEventDataEdit(false); if ($this->EventData->save($this->request->data)) { } diff --git a/web/api/app/Controller/FramesController.php b/web/api/app/Controller/FramesController.php index 6a92671f4..6939856e6 100644 --- a/web/api/app/Controller/FramesController.php +++ b/web/api/app/Controller/FramesController.php @@ -156,6 +156,7 @@ class FramesController extends AppController { throw new UnauthorizedException(__('Insufficient Privileges')); } $this->requireRequestEventEdit(true); + $this->pinRequestId($this->Frame, null); $this->Frame->create(); if ($this->Frame->save($this->request->data)) { return $this->flash(__('The frame has been saved.'), array('action' => 'index')); @@ -178,6 +179,7 @@ class FramesController extends AppController { } $this->requireFrameEdit($id); if ($this->request->is(array('post', 'put'))) { + $this->pinRequestId($this->Frame, $id); $this->requireRequestEventEdit(false); if ($this->Frame->save($this->request->data)) { return $this->flash(__('The frame has been saved.'), array('action' => 'index')); diff --git a/web/api/app/Controller/ZonesController.php b/web/api/app/Controller/ZonesController.php index 3cfa0615a..b7ba1d92e 100644 --- a/web/api/app/Controller/ZonesController.php +++ b/web/api/app/Controller/ZonesController.php @@ -122,6 +122,7 @@ class ZonesController extends AppController { throw new BadRequestException(__('MonitorId is required')); } $this->requireMonitorEdit($monitorId); + $this->pinRequestId($this->Zone, null); $zone = null; @@ -169,6 +170,7 @@ class ZonesController extends AppController { throw new UnauthorizedException(__('Insufficient Privileges')); return; } + $this->pinRequestId($this->Zone, $id); $this->requireMonitorEdit($this->zoneMonitorId($id)); $monitorId = $this->requestMonitorId(); if ($monitorId !== null) $this->requireMonitorEdit($monitorId);