feat: add ZM_LOG_BROWSER_EXTENSIONS config to optionally log extension errors refs #3340

#4914 unconditionally drops Javascript errors and CSP violations sourced from
browser extensions. Some operators want to know when a plugin is touching the
ZoneMinder tab, so gate the suppression behind a config entry.

Add ZM_LOG_BROWSER_EXTENSIONS (boolean, default no) in the logging category.
It is exposed to JS by the existing non-private-config loop in skin.js.php as
the string '0'/'1' (the same convention as ZM_LOG_INJECT), so logger.js only
filters extension sources when it is '0'. Default behaviour is unchanged:
extension noise stays out of the log.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Isaac Connor
2026-06-18 18:35:57 -04:00
parent cb56b810ab
commit cd3aaff0fd
2 changed files with 24 additions and 3 deletions

View File

@@ -1383,6 +1383,20 @@ our @options = (
type => $types{boolean},
category => 'logging',
},
{
name => 'ZM_LOG_BROWSER_EXTENSIONS',
default => 'no',
description => 'Log Javascript errors and CSP violations from browser extensions',
help => q`
Browser extensions (ad blockers, password managers, etc.) inject scripts into
every page. Their Javascript errors and Content-Security-Policy violations are
not ZoneMinder's code and normally just spam the web_js log, so by default they
are ignored. Enable this if you want to see when a browser extension is touching
the ZoneMinder tab, at the cost of noisier logs.
`,
type => $types{boolean},
category => 'logging',
},
{
name => 'ZM_LOG_DEBUG',
default => 'no',

View File

@@ -36,14 +36,21 @@ if (!window.console) {
// Browser extensions (ad blockers, password managers, etc.) inject scripts
// into every page; their errors and CSP violations are not ZoneMinder's and
// would otherwise spam the log. Ignore anything sourced from an extension.
// would otherwise spam the log. Ignore anything sourced from an extension,
// unless ZM_LOG_BROWSER_EXTENSIONS is enabled (useful to detect a plugin
// touching the ZoneMinder tab).
// ZM_LOG_BROWSER_EXTENSIONS is emitted as the string '0'/'1' (boolean config),
// matching the ZM_LOG_INJECT convention above.
const logExtensionSources =
(typeof ZM_LOG_BROWSER_EXTENSIONS !== 'undefined') && (ZM_LOG_BROWSER_EXTENSIONS !== '0');
function isExtensionSource(url) {
return (typeof url === 'string') &&
/^(moz-extension|chrome-extension|safari-web-extension|safari-extension|webkit-masked-url)(:|$)/.test(url);
}
window.onerror = function(message, url, line, col, error) {
if (isExtensionSource(url)) return;
if (!logExtensionSources && isExtensionSource(url)) return;
if (!message || message === 'Script error.') {
message = 'Script error (no details available, possibly cross-origin)';
}
@@ -55,7 +62,7 @@ window.onerror = function(message, url, line, col, error) {
};
window.addEventListener("securitypolicyviolation", function logCSP(evt) {
if (isExtensionSource(evt.sourceFile) || isExtensionSource(evt.blockedURI)) return;
if (!logExtensionSources && (isExtensionSource(evt.sourceFile) || isExtensionSource(evt.blockedURI))) return;
const level = evt.disposition == "enforce" ? "ERR" : "DBG";
let message = evt.blockedURI + " violated CSP " + evt.violatedDirective;