Repository navigation
Seek and Destroy 1.38.5
RPM packages: the 1.38.5 RPM builds failed on Rocky Linux 8, so no 1.38.5 RPMs were published. Use 1.38.6, which is 1.38.5 with that build fix.
ZoneMinder 1.38.5 Release Notes
This is a maintenance release for the 1.38 series. It is primarily a security
release: it fixes 19 reported vulnerabilities, including an authenticated remote
code execution, a heap overflow and a stack overflow, as well as cross-monitor
authorization bypasses throughout the web UI and API. All 1.38 users should upgrade.
⚠️ Upgrade notes
- Run
zmupdate.pl -fafter upgrading (the packages do this for you). It now
replaces aZM_AUTH_HASH_SECRETthat is empty or still the shipped default with a
random secret. Token (JWT) authentication is refused while the secret is the
public default, so on a source install that skips this step API logins answer 403
until a secret is set in Options → System. - The 1.38.5 database update adds an index on
Sessions.accessand moves Controls
rows from the removedonvifprotocol module toONVIF. - The API no longer answers actions a controller does not implement with generic
handlers. Such requests, including Controls add/edit and Configs add, now return
404. - Adding a monitor to a group, removing it, re-parenting the group or deleting it
now requires edit permission on each monitor affected, in both the web UI and
the API. - Forcing, cancelling or disabling a monitor's alarms now requires edit permission
on that monitor. ZM_GO2RTC_PATHmay now be given as the go2rtc server address (http://host:1984)
or its API path (http://host:1984/api); before, only the/apiform worked.
Key Highlights
🔒 Security & Hardening
Code execution and memory safety
- Request data could invoke arbitrary object methods such as
save()and
execute(); through the filter debug modal this was authenticated OS command
execution. Only field accessors can now be set from request data (GHSA-vvx7-ghpv-jq98) Image::ReadJpeg()decoded a larger JPEG into the monitor-sized buffer, a heap
overflow reachable by whoever can write a File monitor's source; File monitors
now also reject a mismatched size (GHSA-rpp4-xmqm-84ff)- RTSP handshake field parsing is bounded (GHSA-xp4c-29hh-2w4j)
- Event video generation runs the encoder without a shell and quotes every
argument (GHSA-pfph-4j9j-7cv7, GHSA-pxq8-5c8j-xf3r) - Filter AutoExecute tag values are kept away from shell parsing, and filter email
frames are extracted without a shell - Stored filter terms are validated and quoted in zmfilter (GHSA-p8h3-4x5c-cv7p)
- The monitor id is validated before it reaches the Janus PIN shell command
- Event DefaultVideo and Name are treated as filenames everywhere, so they cannot
escape the event directory
Authentication
- The shipped default
ZM_AUTH_HASH_SECRETis replaced with a random secret on
upgrade and refused for token signing and verification (GHSA-wmcc-x64g-jr84) /api/host/loginissues the token to the account that authenticated
(GHSA-m77q-66v7-j3fq)X-Forwarded-Foris trusted for auth hash IP binding only from configured
proxies (GHSA-72rf-54rm-798c)- Every AJAX request requires authentication regardless of view (GHSA-vvw3-j4p4-4rgx)
- A non-string request parameter no longer satisfies authentication
- Password migration in
zmupdate.plgave every user the same bcrypt salt; each now
gets a random one, and legacy hashes are rehashed on every password login
Access control
- Event delete, archive and edits are authorized on the event's own monitor
(GHSA-34x2-mw89-c52f) - Frames and EventData API adds require edit on the target event (GHSA-993c-fc6p-hpxg)
- Zone API writes and reads require permission on the zone's monitor
(GHSA-f8h6-62c9-x6qr) - zms event streams are authorized on the event's own monitor, not a monitor id the
client supplies (GHSA-4r2m-68p2-phf7) - The Config API requires System permission and withholds secrets (GHSA-m896-3fc6-2jf5)
- API edits can no longer redirect a save to another record by an Id in the request body
- The per-monitor ACL is applied throughout the Events API (search, console counts,
add, edit), and a user denied every monitor now sees none rather than all of them - Snapshots and Tags no longer expose or attach events from denied monitors
- Group membership changes require edit on each monitor they move
- Log deletion, ZonePresets, the monitor daemon actions, control definitions and host
load endpoints check permissions; user preferences answer to their owner
Injection, XSS and request hardening
- Monitor fields written into page scripts are escaped (GHSA-r44j-mvj8-cg9w)
- The nested filter id is normalised and escaped (GHSA-wqmm-rmvc-pc7r)
filter[query]is escaped in export column links (GHSA-626g-6988-pv4p)- Names and paths are escaped wherever they are written into HTML and JavaScript
- The image proxy no longer follows redirects past its SSRF guard, connects to the
address it checked, requires a CSRF token and refuses cross-site requests
(GHSA-v2qc-p8cq-g4pc) view_videovalidates HTTP Range requests instead of trusting the client's offsets- Client-supplied web log fields are stripped of control characters
- Sessions are stored only for clients that carry a session cookie, and the session
garbage collector uses an index and a two-phase delete - The Perl scripts' taint-safe PATH no longer adds group-writable directories and is
configurable
🐛 Correctness & Recording
- zms can stream the event a monitor is currently recording
- A zms that ends on its ttl or frame count exits instead of waiting forever
- MonitorStream playback state is shared safely with the command thread (#4939)
Image::Fill,Outline,DeltaandOverlaywalk rows by linesize, fixing
striped alarm highlights on widths whose rows are not 32-byte aligned- The blob labeller no longer runs out of tags and abandons the frame
- Hard links fall back to rename on filesystems without them, and EPERM on FreeBSD is
not mistaken for missing support (#5048) - OnDemand capture keeps its last image available while paused
- Deadlocked queries are retried with bounded backoff
- Storage DiskSpace is adjusted atomically
- A lock stops a second zmcontrol server per monitor (#4423)
- zmfilter's start delay applies to the daemon it was meant for, and zmupdate and
zmfilter reconnect to the database instead of pinging it - ONVIF: VideoSource token discovery, 401 handling on PullMessages, subscription
renewal for short lifetimes, and no busy-polling of cameras that don't hold
PullMessages - The lowercase
ZoneMinder::Control::onvifmodule, which collided withONVIF.pmon
case-insensitive filesystems, is removed (#5122) - Config type mismatches are non-fatal
- zmupdate records each migration's own version as it goes, so a failed upgrade resumes
where it stopped, and it no longer applies migrations newer than the installed version - go2rtc and Janus no longer point at ZoneMinder's RTSP restream when
ZM_MIN_RTSP_PORT
is not set, which gave them an unplayable port-0 URL; they use the camera stream
🖥️ Web UI
- Prev/next event no longer loops between two events that start in the same second
- The events table reports a failed query instead of loading forever (#3301)
- Montage review keeps tracking events that are still recording and aborts every
in-flight query; monitor names are no longer shown as HTML entities - The cycle view no longer leaks a timer on every restore (#5135)
- The Control view sends PTZ commands to the monitor's own server
- Event playback rate buttons stay within the rate list;
ZM_WEB_SHOW_PROGRESSis honoured - The timeline view no longer fails for users who cannot view every monitor
- Monitor deletion reports a failure instead of ignoring it (#4215)
- CSV monitor import works again (#4962); export downloads get a proper filename
- The Events entry is back in the classic navbar
- A monitor left on the Auto player no longer pops up "ZM_GO2RTC_PATH is empty" when
go2rtc is not configured; the next player is used instead
🔌 API
- States can be viewed, edited and deleted by Id
- Monitors are soft-deleted like the console does
- An unknown filter field answers 400 naming it instead of 500
- The
DateTimefilter term works - The CakePHP cache prefix includes the ZoneMinder version, so an upgrade does not
reuse a stale cache
📦 Platform & Packaging
- OpenBSD rc.d script and Apache configuration (#5152)
ZM_SERVER_NAMEresolves to its server id- The gnutls libcurl runtime library is listed in the Debian Depends
- The zmrepo source is not added when a PPA already provides ZoneMinder (#4945)
- Ubuntu 26.04 builds
Credits
Thanks to everyone who reported security issues through GitHub's private vulnerability
reporting: Return-Zer0, Haind03, DavidKorczynski (Ada Logics; found by Anthropic using
Claude), 4n86rakam1, qianyuzz, alex131125, SounLabs, phuongmai1212, jasonbernier and
alham-rizvi. The corresponding advisories are published alongside this release.
Full Changelog: 1.38.4...1.38.5