This release includes 8 security fixes for security teams reviewing exposed deployments.
Topics
+3 more
Affected surfaces
ReleasePort's take
Moderate signalThe release adds critical security hardening across APIs, command execution paths, and authentication while improving ONVIF camera handling.
Why it matters: Security severity scores of 95 for privilege escalation fixes and 80+ for eval removal, URL sanitization, SQLβinjection validation, SSRF mitigation, and XSS prevention; operators must upgrade to protect exposed endpoints.
Summary
AI summaryBroad release touches π Security & Hardening, Web Interface & API, π― ONVIF Improvements, and π Authentication & Permissions.
Changes in this release
| Type | Severity | Summary | CVE |
|---|---|---|---|
| Security | Critical |
Fixes API privilege escalation (RCE) by adding System=Edit RBAC checks to ConfigsController edit/delete. Fixes API privilege escalation (RCE) by adding System=Edit RBAC checks to ConfigsController edit/delete. Source: llm_adapter@2026-05-27 Confidence: high |
β |
| Security | Critical |
Removes eval() in FilterTerm SystemLoad/DiskPercent/DiskBlocks evaluation, replacing it with a safe compare() method and operator allowlist. Removes eval() in FilterTerm SystemLoad/DiskPercent/DiskBlocks evaluation, replacing it with a safe compare() method and operator allowlist. Source: llm_adapter@2026-05-27 Confidence: high |
β |
| Security | High |
Sanitizes monitor Device path to prevent command injection (GHSA-g66m-77fq-79v9). Sanitizes monitor Device path to prevent command injection (GHSA-g66m-77fq-79v9). Source: llm_adapter@2026-05-27 Confidence: high |
β |
| Security | High |
Escapes URLs in camera probe wget() to prevent command injection (GHSA-745h-vg7c-73cg). Escapes URLs in camera probe wget() to prevent command injection (GHSA-745h-vg7c-73cg). Source: llm_adapter@2026-05-27 Confidence: high |
β |
| Security | High |
Hardens onvifprobe.php, zmvideo.pl invocation, MonitorsController zmu calls, HostController du, event export, and download/export pipelines with escapeshellarg() and input sanitization. Hardens onvifprobe.php, zmvideo.pl invocation, MonitorsController zmu calls, HostController du, event export, and download/export pipelines with escapeshellarg() and input sanitization. Source: granite4.1:30b@2026-05-27-audit Confidence: low |
β |
| Security | High |
Validates FilterTerm operators, intval's monitor IDs, restricts getFormChanges column keys, and dbEscapes MIME fields to prevent SQL injection. Validates FilterTerm operators, intval's monitor IDs, restricts getFormChanges column keys, and dbEscapes MIME fields to prevent SQL injection. Source: granite4.1:30b@2026-05-27-audit Confidence: low |
β |
| Security | High |
Restricts image.php proxy URL scheme to http/https only, mitigating SSRF. Restricts image.php proxy URL scheme to http/https only, mitigating SSRF. Source: granite4.1:30b@2026-05-27-audit Confidence: low |
β |
| Security | High |
Removes reflected user input from multiple AJAX sinks and sanitizes export filenames/connkeys at the boundary, preventing XSS. Removes reflected user input from multiple AJAX sinks and sanitizes export filenames/connkeys at the boundary, preventing XSS. Source: granite4.1:30b@2026-05-27-audit Confidence: low |
β |
| Security | Medium |
Uses HTTP_X_FORWARDED_FOR consistently for auth-hash validation when AUTH_HASH_IPS is enabled, fixing reverseβproxy authentication bypass. Uses HTTP_X_FORWARDED_FOR consistently for auth-hash validation when AUTH_HASH_IPS is enabled, fixing reverseβproxy authentication bypass. Source: granite4.1:30b@2026-05-27-audit Confidence: low |
β |
| Breaking | High |
Makes the C++ User class roleβaware, consulting Role_Monitors_Permissions and related tables for liveβstream access. Makes the C++ User class roleβaware, consulting Role_Monitors_Permissions and related tables for liveβstream access. Source: llm_adapter@2026-05-27 Confidence: high |
β |
| Performance | Medium |
Defaults ffmpeg decoder thread_count to 2, roughly halving software 1080p H.264 decode time. Defaults ffmpeg decoder thread_count to 2, roughly halving software 1080p H.264 decode time. Source: llm_adapter@2026-05-27 Confidence: high |
β |
| Bugfix | High |
Resolves VideoStore useβafterβfree by holding filename by value and adding null checks. Resolves VideoStore useβafterβfree by holding filename by value and adding null checks. Source: llm_adapter@2026-05-27 Confidence: low |
β |
| Bugfix | High |
Prevents MonitorStream segfault by adding monitor mutex to block sharedβmemory access during loadMonitor(). Prevents MonitorStream segfault by adding monitor mutex to block sharedβmemory access during loadMonitor(). Source: llm_adapter@2026-05-27 Confidence: low |
β |
| Bugfix | Medium |
Adds ONVIF subscription cleanup before reβSubscribe to stop NotAuthorized loops on Hikvision/Reolink cameras. Adds ONVIF subscription cleanup before reβSubscribe to stop NotAuthorized loops on Hikvision/Reolink cameras. Source: llm_adapter@2026-05-27 Confidence: high |
β |
| Bugfix | Medium |
Derives Event end time from StartDateTime + Length when EndDateTime is missing, preventing montage review errors. Derives Event end time from StartDateTime + Length when EndDateTime is missing, preventing montage review errors. Source: llm_adapter@2026-05-27 Confidence: high |
β |
Full changelog
ZoneMinder 1.38.2 Release Notes
This is a maintenance release that includes security fixes, crash and memory-safety fixes, and a wide range of bug fixes for ZoneMinder 1.38.
Key Highlights
π Security & Hardening
- API privilege escalation (RCE) - Added System=Edit RBAC checks to ConfigsController edit/delete and hardened ffmpeg path handling in Event.php
- GHSA-g66m-77fq-79v9 - Sanitized monitor Device path to prevent command injection
- GHSA-745h-vg7c-73cg - Escaped URLs in camera probe wget() to prevent command injection
- eval()-based RCE in filters - Replaced eval() in FilterTerm SystemLoad/DiskPercent/DiskBlocks evaluation with a safe compare() method and operator allowlist
- Command injection - Hardened onvifprobe.php, zmvideo.pl invocation, MonitorsController zmu calls, HostController du, event export, and download/export pipelines with escapeshellarg() and input sanitization
- SQL injection - Validated FilterTerm operators and collate, intval'd AlarmedZoneId/monitor IDs, restricted getFormChanges column keys, dbEscape'd MIME fields
- SSRF - Restricted image.php proxy URL scheme to http/https only
- XSS sanitization series - Removed reflected user input from add_monitors, device, event, events, log, and filterdebug AJAX sinks; sanitized export filename/connkey at the AJAX boundary; suppressed raw SQL error text in events ajax responses
- Auth hash bypass with reverse proxy - HTTP_X_FORWARDED_FOR is now used consistently in both PHP and C++ auth-hash validation when AUTH_HASH_IPS is enabled
- Reverse-proxy username handling - Auth relay now carries the username so zms validates against the indexed Username column instead of brute-iterating users
- Bumped
firebase/php-jwtfrom 6.0.0 to 7.0.0
π Crash & Memory Safety
- VideoStore use-after-free - filename is now held by value; destructor guards against null oc when open() bailed early (refs #4757)
- MonitorStream segfault - Added monitor mutex to block shared-memory access during loadMonitor() remap; bail early when monitor failed to load
- MPEG stream codec failure - Prevented segfault when the codec fails to open
- MonitorLink fd leak - connect() now calls disconnect() first, avoiding "Too many open files" under repeated reconnect attempts
- VideoStore fd/codec leaks - Free video_out_ctx after failed avcodec_open2; clean up codec/hw_device contexts on setup_hwaccel failure; skip flush_codecs when no frames were sent
- Daemon FD reuse memory corruption - Daemonization now redirects stdin/stdout/stderr to /dev/null instead of closing them, avoiding libx264 writing into a reused stderr FD
- Null pointer guards - StorageId in Monitor::Load, ModelId in Monitor::Model(), evtStream element, monitor div in video-stream.js
- Empty events in ONDEMAND mode - Removed write-index guard that caused rapid Pause/Play cycling and ~2 empty events per second
π― ONVIF Improvements
- Subscription cleanup before re-Subscribe - Stops the NotAuthorized loop caused by leaked pull-point slots on Hikvision/Reolink cameras
- OOB read on negative gsoap codes - Replaced SOAP_STRINGS[] array with a switch covering the codes seen in practice (SOAP_EOF, network/SSL block, SOAP_STOP) (fixes #4842)
- Credential fallback chain - Falls back to Monitor ONVIF_Username/Password then Monitor User/Pass when ControlAddress lacks auth
- 'Use ONVIF' badge - Only displayed on the console when the listener is actually enabled
π₯ Recording & Playback Correctness
- AV_NOPTS_VALUE handling in VideoStore - Synthesizes monotonic DTS for video passthrough; reorder queue and audio first-DTS no longer compare AV_NOPTS_VALUE sentinels
- DTS backward jump detection - FfmpegCamera now triggers reconnect on >10s DTS rollback, ending the minutes-long warning storm after stream restarts
- Event end time - Derived from StartDateTime + Length when EndDateTime is missing (crashed/killed zmc), so montage review no longer paints a bar across hours of down-time
- incomplete.mp4 playback - C++ handler, view_video.php, and image.php now load incomplete event files; mp4 export end-timestamp fixed for multi-event downloads (closes #4767, #4774)
- HTTP Range header parsing - Correctly handles
bytes=start-end,bytes=start-, andbytes=-suffix; fixes ERR_CONTENT_LENGTH_MISMATCH on HEVC mp4 playback in Chrome (fixes #4777) - Content-Range header - Added missing dash separator
- Event::delete deadlock retry - Wraps the delete transaction in READ COMMITTED with rollback + retry on errno 1213 instead of committing through the deadlock
- Polygon fill - Scan-line fill now steps active edges by pairs, fixing non-convex zones whose concave gaps were incorrectly marked inside the zone
- Polygon clamp - Percentage polygons clamp to width-1/height-1, stopping spurious "polygon hi_x >= image width" warnings
- Zone stats percentage - Convert Area from percentage coordinate space (0-10000) to pixel area before calculating percentages
- alarm_frame_count in ready_count - Stops "Hit end of packetqueue before satisfying pre_event_count" warnings when pre_event_count is 0 but alarm_frame_count is set
π Authentication & Permissions
- Role-based stream access - The C++ User class is now role-aware and consults Role_Monitors_Permissions, Role_Groups_Permissions, and User_Roles base permissions, matching the PHP visibleMonitor() logic
- Stale auth hash on long-running montage - MonitorStream.js refreshes the auth hash before restarting streams on tab visibility change and after the idle modal
- Auth relay - Username included in relay; empty auth_relay no longer produces double
&&in zms URLs; warns on user mismatch instead of silently falling back - Trigger reliability - zmTriggerEventOn now writes trigger_state last, fixing a ~1/3 trigger acceptance rate
ποΈ Build & PHP 8
- a2enmod rewrite - Debian/Ubuntu postinst now enables mod_rewrite (was incorrectly calling
a2enmod cgi), fixing install failures - PHP 7 polyfill - str_starts_with/str_ends_with/str_contains polyfilled at the top of functions.php so PHP 7 installs don't fatal on event.php
- PHP 8 GD handling - imagedestroy() now called conditionally; GDImage memory release forced on PHP >= 8.0
- www-data video group - Debian/Ubuntu postinst adds www-data to video and dialout groups so zmc can open /dev/video* on fresh installs (refs #4642)
- FreeBSD support - Top command parsing fixed for FreeBSD 13.5; /proc/meminfo presence check; removed unnecessary kFreeBSD arch checks; FreeBSD arm build fixes
- ZM_NO_CURL=ON build - Added HAVE_LIBCURL preprocessor guards throughout zm_monitor.h/cpp and zmc.cpp
Detailed Changes
Core Engine
- Default ffmpeg decoder thread_count to 2 (roughly halves software 1080p H.264 decode time)
- Flush decoder_queue on decoder thread exit to avoid stale latency offset across reconnect
- Reset last_write_index in Pause() to restore DECODING_ONDEMAND bootstrap
- Added CLOSE_DURATION event close mode handling (was silently falling back to CLOSE_IDLE)
- Event-id latch on linked monitor score detection so brief alarm cycles between analysis ticks are no longer missed
- Reduced linked monitor reconnect throttle from 60s to 1s
- Used
+1instead of+last_durationfor equal-DTS fixup
Camera Support
- FFmpeg: Use CxxUrl for credential injection (replaces fragile substr-based string manipulation)
- Reolink: Handle HTTP-to-HTTPS 302 redirect during login (LWP::UserAgent does not follow redirects on POST)
- V4L2: Treat ENOTTY like EINVAL when querying JPEG compression options (some drivers return ENOTTY)
- Added SSL certificate verification fallback to the base Control class β retries with verification disabled on SSL errors, applies to get/put/post
Web Interface & API
- pushState URL state management on the events page so filter state is shareable and browser back/forward restores it
- Mobile layout fixes for monitor config and watch views (CSS specificity on
:first-child) - Don't blank the screen between events when animations are off
- Alarm/idle border applied only to
.imageFeed, not nested img/video elements β fixes extra borders and streams jumping around - Stream URL state: include port in Server URL methods for port-forwarded setups (fixes #4675); honor explicit port argument in urlToApi single-server case; urlToApi falls back to location.host
- Thumbnail overlay scale calculated from actual monitor dimensions instead of hardcoded scale=75
- Montage review playback smoothness, fractional-seconds preserved through mmove/setSpeed, video seek overshoot fix on initial AVSEEK_FLAG_FRAME
- montagereview cookie stores speed value instead of index; changeFilters guards against NaN from invalid date input
- getTracksFromStream moved to skin.js so it can also be used on recorded events; added Go2RTC variant; restart MSE thread on any appendMseBuffer error (not just QuotaExceededError)
- VolumeSlider: noUiSlider properly destroyed when switching player or monitor;
#volumeControlsnow always includes the monitor ID - Event page: VID vs MJPEG playClicked/pauseClicked separation; toggleZones listener assigned after Event page loads
- MSE addSourceBuffer guarded against detached MediaSource when WebRTC wins the race in video-rtc.js
- RTSP2Web RTC errors restart the stream instead of killing it
- z-index ordering for zones SVG overlay corrected (cannot exceed .zonePoint index)
- Sort events by Tags column alias rather than T.Name (which is out of scope in the aggregated query)
- navbar_type now saved in cookies
- Move tag-related commands to canView(Events) instead of canEdit(Events); add Create to canEdit
- Filter debug modal: strip SKIP LOCKED from EXPLAIN so MySQL accepts the query
- Don't add postLoginQuery to the URL when empty
- Fixed image.php
.$fileconcatenation error
Database & Filters
- Filter::Sql() now clears accumulated state (PostSQLConditions, HasDiskPercent, HasDiskBlocks, HasSystemLoad) before rebuilding β previously grew unboundedly
- Filter::Execute uses
prepareinstead ofprepare_cachedsince the SQL changes every cycle (the cache never hit; entries leaked one per distinct substituted value) - zmfilter uses the minimum per-filter delay instead of the last filter's delay, so no filter oversleeps its ExecuteInterval; overdue warning now uses the unclamped delay and includes the filter name
- Handle PostSQLConditions being an empty array; don't Fatal on SQL prepare errors
- zmDbDo error logging substitutes all SQL placeholders (was only substituting the first)
- Log bind params correctly when SQL contains literal
%characters
Configuration & Logging
- Increased potential config line size β HTML snippets can easily exceed 256 bytes
- Enriched zms auth-failure warning with diagnostic fields to distinguish stale hash, missing auth, disabled user, and IP mismatch
- Removed Warn() in favour of Warning() (fixes #4724)
- Log commands used when updating the database
Scripts & Tools
- zmcontrol/Control.pm: Base class SSL fallback applied across get/put/post
- zmtelemetry/zmu: Daniel Caujolle-Bert refactors using ZoneMinder::Config, ZM_PATH_UNAME, lc()/chomp()/qx, eq instead of ==
- a2enmod: Postinst fix from
cgitorewrite
- QP-encode plain-text email body so URLs with
%EP%/%EPS%/%EPI%substitution tags survive transit (mail clients had been QP-decoding=NNdigit pairs)
Miscellaneous
- Advanced RtspServer pin to a0715995 (correct RTP marker bit from frame.last; cmake_minimum_required to 3.10)
- Don't pass null as the first parameter to strtotime() (PHP 8.1 deprecation)
- Added ZoneMinder.spec from the OBS project
Platform-Specific Changes
FreeBSD
- Fixed top command parsing (tested on FreeBSD 13.5)
- Check for /proc/meminfo before reading (does not exist on FreeBSD)
- Removed unnecessary kFreeBSD arch checks (was amd64/i386 only)
- FreeBSD arm build fixes
CI/CD
- Updated deb and aarch64 deb package workflows for the release-1.38 branch
- Renamed proposed rsync target to proposed-1.38
- Use
-r=<tag>for release builds instead of-s=CURRENT - Dynamic branch and deploy targets in deb package workflows
- RPM workflows fire on release-1.38 and derive deploy target from ref
- Package workflows now also fire on tag pushes so stable repo deploys actually run
- ESLint workflow updated to v9 for flat config support
- ESLint config synced to ESLint 9 flat config format
- Bumped GitHub Actions: download-artifact v8, upload-artifact v7, crazy-max/ghaction-import-gpg v7
Upgrade Notes
- Reverse-proxy users with AUTH_HASH_IPS enabled: Authentication now consults HTTP_X_FORWARDED_FOR (falling back to REMOTE_ADDR), so the per-monitor auth hash matches across PHP and C++ when ZoneMinder sits behind a proxy. If you carried custom ZoneMinder-side workarounds for this (e.g. disabling AUTH_HASH_IPS, custom auth_relay code), those can now be removed. Apache/nginx-side RemoteIPHeader / proxy_set_header configuration should stay as-is.
- Role-based permissions: Users who receive camera permissions via Roles will now correctly get live stream access through zms β previously the C++ User class only checked direct user permissions.
- PHP 8 users: GDImage memory handling is now PHP 8-aware. No action needed, but resource usage should be lower.
- Debian/Ubuntu fresh installs: postinst now adds www-data to the
videogroup and runsa2enmod rewrite(notcgi). Existing installs may want to verify these manually. - ONVIF subscription leaks: If cameras were previously stuck in NotAuthorized loops after a few hours, this should now self-recover without a zmc restart.
Contributors
This release includes contributions from the ZoneMinder development team and community members who reported bugs, tested fixes, and provided feedback.
Full Changelog: https://github.com/ZoneMinder/zoneminder/compare/1.38.1...1.38.2
Security Fixes
- GHSA-g66m-77fq-79v9 β Sanitized monitor Device path preventing command injection
- GHSA-745h-vg7c-73cg β Escaped URLs in camera probe wget() to prevent command injection
- API privilege escalation (RCE) fixed by adding System=Edit RBAC checks to ConfigsController edit/delete and hardening ffmpeg path handling
- eval()-based RCE in filters replaced with safe compare() method and operator allowlist
- Multiple commandβinjection mitigations across onvifprobe.php, zmvideo.pl, MonitorsController zmu calls, HostController du, event export/download pipelines using escapeshellarg() and input sanitization
- SQL injection fixes: validated FilterTerm operators/collate, intval'd IDs, restricted getFormChanges keys, dbEscape'd MIME fields
- SSRF mitigation: image.php proxy URL scheme limited to http/https only
- XSS series sanitized in add_monitors, device, event, events, log, filterdebug AJAX sinks and export filename/connkey
Weekly OSS security release digest.
The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.
No spam, unsubscribe anytime.
Share this release
About zoneminder
ZoneMinder is a free, open source Closed-circuit television software application developed for Linux which supports IP, USB and Analog cameras.
Related context
Related tools
Earlier breaking changes
- v1.38.3 Makes C++ User class roleβaware, consulting Role_Monitors_Permissions and matching PHP visibleMonitor() logic (roleβbased stream access).
Beta — feedback welcome: [email protected]