68 Commits
Author SHA1 Message Date
singhharsh1708 9443c79f34 fix: use the interpolation syntax that survives PHP 9 2026-09-07 13:11:51 +05:30
Isaac Connor 4d82e1d385 fix(api): answer 400 naming the field instead of 500 on an unknown filter
FilterComponent::buildFilter() checked the operator against an allow list
but never the field, so an unrecognised name went straight into the SQL:

  GET /api/events/index/MonitorName =:Monitor-1.json
  PDOException SQLSTATE[42S22]: Column not found: 1054
    Unknown column 'MonitorName' in 'where clause'   -> HTTP 500

A caller filtering on a monitor-scoped attribute (MonitorName, Monitor,
Rack, Experiment, Server, Storage) or simply mistyping a column got an
opaque 500 with a stack trace and nothing naming the offending field.

Take an optional list of permitted fields and reject anything else with
BadRequestException.  EventsController::index() passes the Events columns
from the model schema plus the two names it resolves itself: DateTime, the
pseudo-attribute it turns into an overlap test, and GroupId, served by the
Groups_Monitors join.  A field written Model.Field is checked on the column.
The two other callers of buildFilter pass no list and are unchanged.

The invalid-argument and invalid-operator paths threw a plain Exception,
which was also a 500 for what is bad input; they throw BadRequestException
now too.

Verified against a live install:

  MonitorName / Rack / NotAColumn     400, "Unknown filter field: <name>"
  DateTime >=/<= over a window        200, 7 rows, overlap intact
  Cause, Event.Cause, GroupId         200
  ?limit=2&sort=...&direction=desc    200, 2 rows, pagination unaffected

Plus an assert script over the validation itself, including that a caller
passing no list keeps the previous pass-through behaviour.

Filters sent as a query string rather than named parameters go through a
different branch, on raw $_REQUEST, and are deliberately left unvalidated:
that array also carries auth and cache-busting parameters, so rejecting
unknown names there would break existing callers.  ?MonitorName=x is still
a 500 and wants its own change.
2026-08-28 19:54:07 -04:00
Isaac Connor 0d7abadb61 fix: make the API DateTime filter an overlap test
DateTime is a pseudo-attribute meaning "the event was running then", so a
window over it should select events that overlap the window. The code applied
the term's own operator to both StartDateTime and EndDateTime, which turns the
upper bound into StartDateTime <= max AND EndDateTime <= max -- a containment
test. Any event spanning the end of the window was dropped, which under
continuous recording is most of them, and with events longer than the window,
all of them. Montage review showed an empty timeline as a result.

The lower bound now tests the event's end and the upper bound its start.

The EndDateTime IS NULL allowance is also bounded. It was there so an event
still being written stays visible, but as written it made every crash-orphaned
event ever recorded match every window: montage review was returning events
from two weeks earlier and nothing from the requested hour. An event with no
EndDateTime still has Length, flushed every few seconds by zmc, so
StartDateTime + Length is its effective end; only an event with neither falls
back to NOW(). This is the same expression the Event model already uses for
its EndTimeSecs virtual field.

Verified against a live instance: for a 09:43-10:43 window that had one
overlapping event per monitor, the API returned 8 events for monitor 1, all
crash orphans from 2026-07-26 and none from the window. It now returns the
overlapping event, and montage review draws it and renders its frames.
2026-08-09 21:35:39 -04:00
Isaac ConnorandClaude Opus 4.8 516bab0247 fix: enforce per-monitor ACL on direct event, frame and zone API endpoints
Several API endpoints checked only the coarse Events/Monitors permission
and not the per-monitor object ACL, so a user explicitly denied a monitor
could still reach that monitor's objects by addressing them directly:

- EventsController::edit() and ::delete() checked Events=Edit but never
  called canEdit() on the event, so any event could be mutated or deleted
  by Id.
- FramesController only guaranteed Events != None in beforeFilter().
  view() returned any frame by Id, and edit()/delete() mutated frames
  without requiring Events=Edit or checking the parent event at all.
- ZonesController::forMonitor() listed zones for any monitor Id.

Resolve the owning object and apply the same canView()/canEdit() checks
the normal read paths already use. Frames are addressed by their own Id,
so their parent event is looked up to reach the monitor ACL.

Refs GHSA-hw39-qpjw-p7cg.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 11:51:54 -04:00
Isaac ConnorandClaude Opus 4.8 3d5de347a4 fix: enforce authorization on API events/archive action
The REST API archive action toggled an event's Archived
(retention-protection) flag with no authorization beyond the
controller's coarse "Events permission is not None" gate. Any
authenticated read-only user, including one restricted to a subset of
monitors, could flip the retention state of any event by enumerating
event ids, and the action was reachable over GET (CSRF-able).

Gate the write by direction: archiving (protects from purge) requires
view access via Event::canView(); un-archiving (re-exposes to purge)
requires edit access via Event::canEdit(). Both enforce the per-monitor
object-level ACL. Restrict the action to POST/PUT to block CSRF.

Addresses GHSA-5v9h-ww7p-hxgv.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 12:32:51 -04:00
Isaac Connor 61c4c6606b perf: scope Event neighbor queries to Id only
The view() action sets recursive=1 on the Event model, which the
subsequent find('neighbors') calls inherited. That made each of the four
neighbor lookups (prev/next, prevOfMonitor/nextOfMonitor) SELECT every
column from Events plus LEFT JOIN Monitor and Storage, then fire a
separate Frames hasMany query per neighbor row. Only Event.Id is used
downstream.

Pass fields=Event.Id and recursive=-1 on each neighbor call so the
generated SQL is just:

  SELECT Event.Id FROM Events AS Event WHERE Event.Id < ?
    ORDER BY Event.Id DESC LIMIT 1

The per-monitor variant uses Events_MonitorId_idx which already covers
(MonitorId, Id) via InnoDB's implicit PK suffix, so no schema change is
needed.
2026-05-15 23:01:00 -04:00
Isaac ConnorandClaude Opus 4.6 ffe6362dc3 fix: harden web interface against injection and SSRF vulnerabilities
FilterTerm.php:
- Replace eval() with safe compare() method for SystemLoad, DiskPercent,
  and DiskBlocks filter conditions (RCE via crafted op/val)
- Validate operator against allowlist in constructor
- Sanitize collate field to alphanumeric/underscore only (SQLi)

onvifprobe.php:
- Use escapeshellarg() on interface, device_ep, soapversion, username,
  and password arguments passed to execONVIF() (command injection)

Event.php:
- Use escapeshellarg() on all arguments to zmvideo.pl instead of
  escapeshellcmd() on the whole command (command injection via format)
- Anchor scale regex with ^ and $ to prevent partial matches

image.php:
- Restrict proxy URL scheme to http/https only (SSRF via file:// etc)

filterdebug.php:
- Use already-sanitized $fid instead of raw $_REQUEST['fid'] (XSS)

MonitorsController.php:
- Use escapeshellarg() on token, username, password, and monitor id
  in zmu shell command instead of escapeshellcmd() on whole command

HostController.php:
- Use escapeshellarg() on path in du command (command injection via mid)
- Remove space from daemon name allowlist (argument injection)

EventsController.php:
- Remove single quotes from interval expression regex (SQLi)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 23:30:49 -04:00
Isaac Connor ba0aa29cdb Merge branch 'master' of github.com:ZoneMinder/zoneminder 2026-01-16 14:55:33 -05:00
Isaac Connor 0ecb344723 Don't contain Tag so we don't include Tag and event_Tag in results. 2026-01-05 17:49:46 -05:00
Steve Gilvarry 58a0e68731 Add Tags to event search and return tag data with events 2026-01-05 21:43:27 +11:00
Isaac Connor 9c79b1ad6e Make filtering by Tags.Id work 2025-12-19 17:50:10 -05:00
Isaac Connor 9351835815 Update use of allowedmonitors 2025-11-19 09:24:46 -05:00
Isaac Connor dbbe2cbcb8 Add EndDateTIme IS NULL condition when using DateTime 2025-10-30 13:53:28 -04:00
Isaac Connor 802ccdcaa9 Use event->can_view to restrict viewing event 2025-10-23 13:56:33 -04:00
Isaac Connor 65d51c7d0a Add support for DateTime as a filter, which means either StartDateTime OR EndDateTime. This allows montagereview to include video that starts BEFORE the requested time but ends after 2025-10-23 13:53:57 -04:00
Isaac Connor 1dde898154 Add support for NOT using named params, using query string instead 2025-09-05 12:42:13 -04:00
Isaac Connor d5e81d3c4a Fix incorrect use of eventObjas an array. 2025-07-19 18:29:22 -04:00
Isaac Connor 97877335db Add MaxScoreFrameId to the events table and automatically update it during event creation so we don't have to look it up later. Finding the frame with the max score is actually a very heavy query, so lets not do it. 2025-07-15 16:17:36 -04:00
Isaac Connor c3d4b12286 Do pagination if either page or limit is specified. Otherwise return all 2025-07-11 17:40:54 -04:00
Isaac Connor 7a3a5ca58a If request includes pagination= then restore the old pagination code. 2025-07-09 17:24:02 -04:00
Isaac Connor ce76f953dc Use limit if it is specified 2025-06-21 21:43:01 -04:00
Isaac Connor d97b37f83f Don't do pagination in events.This may break users of API but pagination should only happen if asked for 2025-06-11 14:57:54 -04:00
Isaac Connor 1ab66b10b1 Don't assume findByEventidAndType actually returns a frame. If we are only recoridng, then there will be no alarm frames in the db 2024-01-16 09:50:19 -05:00
Isaac Connor 065bed894c Use empty array instead of null for allowedMonitors 2023-11-24 08:57:35 -05:00
Isaac Connor 6232cfb8b1 Remove 100 limit on response to events index. Handle there not being a next or prev neighbour. 2023-11-16 18:00:30 -05:00
Isaac Connor 8fd33b7f77 Handle case when Alarm frame or MaxScore frame don't exist. 2023-10-15 17:55:44 -04:00
Isaac Connor 8a00be8cc8 Load the ZM::Event using the Event Model data instead of loading by Id which goes back to db. 2023-06-09 10:53:10 -04:00
Isaac Connor 49014ac7fa replace MonitorIds with viewableMonitorIds 2023-04-24 13:53:14 -04:00
stefan 1bd9def63e [API] Fix User variable from array to object
Fix $user variable from array to object to make API work again.
2023-04-23 22:07:24 +02:00
Pliable Pixels 6c3f18a347 allow option to not load the frames DB table 2021-06-27 15:55:10 -04:00
Isaac Connor 6ed006bc60 rename StartTime and EndTime to StartDateTime and EndDateTime in the named query params 2020-11-06 12:25:04 -05:00
Isaac Connor d37bce4e0e Fix missed StartTime to StartDateTime 2020-11-04 18:20:59 -05:00
Pliable Pixels 8ad62b8905 another try at fixing Eventcontroller 2020-09-13 16:43:49 -04:00
Pliable Pixels a261efe0a7 return relative frameId, also sort score frame by lowest frame to avoid bulk frames incase score is 0 2020-03-09 12:04:40 -04:00
Pliable Pixels 3611d147e6 fix score frameID to return ID not Score 2020-03-09 11:25:07 -04:00
Pliable Pixels c5e838fa33 return global frame IDs of snapshot and alarm frames for a specific event 2020-03-09 11:09:20 -04:00
Isaac Connor 76d795f413 add rendering of enums for Events Controller 2019-12-01 12:32:14 -05:00
Pliable Pixels c4dc5f34e4 add event file system path to API (#2639) 2019-06-16 11:59:23 -04:00
Pliable Pixels d270fbd0ad added support for named params to consoleEvents (#2571) 2019-04-09 16:28:46 -04:00
Isaac Connor fa9803d819 Can't use this->data to avoid another db hit. Must load by id 2019-04-01 10:11:56 -04:00
Matthew Noorenberghe abb6ef1688 API: Escape 'named' params for SQLi in two more Event endpoints.
Fixes #2099
2019-03-11 00:21:51 -07:00
Matthew Noorenberghe 056b96f7fc API: Monitor and Event 'index' SQLi. Fixes #2099 2019-03-11 00:21:51 -07:00
Andrew Bauer 3258d8e590 remove ZM_DIR_IMAGES (#2374) 2018-12-29 09:52:58 -05:00
Isaac Connor 23ddc83ad4 fix_2167 (#2168)
* Populate a global  from the session on every request. Use the  object instead of using allowedMonitors in session.

* fix when  gets loaded.

* use  for auth, and add Monitor Edit checks to Zone add/delete/edit

* add back the ZM_OPT_USE_AUTH test for being logged in in AppController

* Update permissions code to use

* change quotes

* Update permission code to use

* Use  instal of session for systemPermission

* deprecate montiorPermision in session

* use  instead of session streamPermission

* move login code back into AppController. Has to be done for every request

* deprecate eventPermission, controlPermission and systemPermission in session.

* handle auth params in query string as well as post

* exit on HUP to free up memory.

* add missing global user

* system should be System
2018-08-08 09:59:46 -04:00
Isaac Connor dc57a3c91c fix spacing/quotes/google code style 2018-07-24 16:41:09 -04:00
Isaac Connor 62edca6dcb add fileSize to the api, and use it to add remote fileSize reporting in includes/Event 2018-05-08 13:33:56 -07:00
Isaac Connor 1a012c62ff Add fileExists to event view 2018-05-07 14:07:03 -07:00
Isaac Connor 85b7baa131 Fix event Groups associations 2017-12-19 18:09:41 -05:00
Isaac Connor d312482a2b add StorageScheme to Storage and Events. Deprecate ZM_USE_DEEP_STORAGE 2017-12-18 12:52:26 -05:00
Isaac Connor dd69bc3d7b Merge branch 'master' into storageareas 2017-12-11 11:39:42 -05:00