{ "schemaVersion": 2, "repository": "Cacti/cacti", "reviewedCommit": "bcb4aaf4ae37ca6bc6b20398f7fdd054505c927c", "scope": "All inventoried findings have per-sink source, current core caller and input-contract decisions. Browser coverage checks representative rendering boundaries. Third-party plugin repositories and arbitrary plugin configurations are outside this repository audit; their untrusted values must still be validated/sanitized before entering HTML/selector/URL configuration APIs.", "evidence": { "browserTests": "tests/e2e/tests/frontend-dependency-security.spec.ts", "codeqlBuild": "Managed assets are built before JavaScript extraction so generated libraries remain scanned.", "branch": "develop; current 1.2.x does not contain the HTMX integration.", "validationMessages": "Cacti formValidate uses the dependency escapeHtml option for title/custom error messages. Browser regression checks initial creation, update, and successful validation; the original setup parses hostile title text as markup in the isolated fixture. No attacker-controlled Cacti title path is established.", "browserCases": 23, "followUpScope": "Default-branch verification of pending fixes after maintainer merge. No scan exclusions or weakened rules; optional HTML APIs retain an explicit trusted-configuration contract.", "branchComparison": "Recorded exact 1.2.x source presence/hashes. The dropdown utility is also present there, with no current core caller found on either branch. This develop PR is API hardening rather than a demonstrated remote vulnerability. The jQuery 4 collection-sort fix targets develop; 1.2.x runtime/CI differs and is not silently backported.", "sharedRenderingBoundary": "include/js/cacti-rendering.js exposes one immutable synchronous menuFragment interface for legacy classic-script widgets. It uses minimal tag/attribute allowlists, explicit link protocol checks and text fallback; owned dropdown and navigation-box plugin HTML use it. Labels use text APIs. This does not sanitize unrelated plugin content APIs.", "ciRepair": "The certificate display regression derives its expected date from the generated certificate validTo_time_t rather than +30 local-calendar days, which differs across daylight-saving transitions." }, "alerts": [ { "number": 622, "rule": "js/xss", "path": "include/js/htmx.js", "observedState": "open", "disposition": "false_positive", "evidence": "URL and HTML are distinct history-item fields; serialization loses field-specific taint. Browser save/restore regression.", "reportedLine": 1606 }, { "number": 620, "rule": "js/unsafe-jquery-plugin", "path": "include/js/jquery.validate/jquery.validate.js", "observedState": "dismissed", "disposition": "false_positive", "evidence": "Current jquery-validation 1.22.1: findByName escapes the attribute selector and returns existing form DOM elements; clean, validationTargetFor and grouped ARIA paths receive those elements, not HTML strings. Browser regression covers hostile field names, all four paths, and preserved ARIA. This conclusion is scoped to the reported selector/element sinks, not message rendering or arbitrary plugin options.", "resolution": "Replace ownership/static-rule rationale with DOM-element and escaped-selector evidence; independent message HTML boundary hardened in formValidate with escapeHtml=true.", "reportedLine": 631 }, { "number": 619, "rule": "js/unsafe-jquery-plugin", "path": "include/js/jquery.validate/jquery.validate.js", "observedState": "dismissed", "disposition": "false_positive", "evidence": "Current jquery-validation 1.22.1: findByName escapes the attribute selector and returns existing form DOM elements; clean, validationTargetFor and grouped ARIA paths receive those elements, not HTML strings. Browser regression covers hostile field names, all four paths, and preserved ARIA. This conclusion is scoped to the reported selector/element sinks, not message rendering or arbitrary plugin options.", "resolution": "Replace ownership/static-rule rationale with DOM-element and escaped-selector evidence; independent message HTML boundary hardened in formValidate with escapeHtml=true.", "reportedLine": 619 }, { "number": 169, "rule": "js/incomplete-sanitization", "path": "include/js/htmx.js", "observedState": "dismissed", "disposition": "fixed_after_reopening", "evidence": "Reopening the obsolete dismissal immediately returned state=fixed; no matching HTMX incomplete-sanitization result exists in current default-branch SARIF.", "reportedLine": 1552 }, { "number": 168, "rule": "js/xss", "path": "include/js/htmx.js", "observedState": "dismissed", "disposition": "false_positive", "evidence": "Same history-item URL/content field confusion, with DOMParser as the reported sink.", "resolution": "Dismissal replaced with source-specific false-positive rationale and PR/browser evidence.", "reportedLine": 532 }, { "number": 167, "rule": "js/incomplete-url-scheme-check", "path": "include/js/htmx.js", "observedState": "dismissed", "disposition": "false_positive", "evidence": "js:/javascript: prefixes select expression evaluation in hx-vals/hx-vars; this is not a request URL scheme check. Cacti forbids evaluation and browser test verifies rejection.", "resolution": "Dismissal replaced with source-specific false-positive rationale and PR/browser evidence.", "reportedLine": 3985 }, { "number": 131, "rule": "js/prototype-pollution-utility", "path": "include/js/pace.js", "observedState": "dismissed", "disposition": "fix_pending_merge", "evidence": "Pace deep merge can follow prototype keys from option objects. Dependency patch blocks those keys; all three original-code mutation tests fail.", "reportedLine": 122 }, { "number": 130, "rule": "js/prototype-pollution-utility", "path": "include/js/billboard.js", "observedState": "dismissed", "disposition": "false_positive", "evidence": "bindThis is called with library-owned Chart.prototype and copies methods into its Chart instance, rather than merging caller options.", "resolution": "Dismissal replaced with source-specific false-positive rationale and PR/browser evidence.", "reportedLine": 21507 }, { "number": 129, "rule": "js/prototype-pollution-utility", "path": "include/js/billboard.js", "observedState": "dismissed", "disposition": "false_positive", "evidence": "mergeObj already rejects __proto__, constructor and prototype; browser test preserves valid data while rejecting prototype keys.", "resolution": "Dismissal replaced with source-specific false-positive rationale and PR/browser evidence.", "reportedLine": 809 }, { "number": 126, "rule": "js/html-constructed-from-input", "path": "include/js/jquery.tablesorter.widgets.js", "observedState": "dismissed", "disposition": "false_positive", "evidence": "Scoped reported sink review: Core initializers enable zebra, not filter/resizable/sticky widgets. ts.css class names are package constants. Optional filter CSS/template options and storage IDs are developer configuration, not cell values. Browser case exercises controlled filter and storage APIs, retaining hostile text. Cookie fallback replacement is a redundant no-op, not HTML sanitization. jQuery 4 collection sort is corrected by the asset build.", "reportedLine": 2774, "inputContract": "Core initializers enable zebra, not filter/resizable/sticky widgets. ts.css class names are package constants. Optional filter CSS/template options and storage IDs are developer configuration, not cell values. Browser case exercises controlled filter and storage APIs, retaining hostile text. Cookie fallback replacement is a redundant no-op, not HTML sanitization. jQuery 4 collection sort is corrected by the asset build.", "sourceEvidence": { "path": "include/js/jquery.tablesorter.widgets.js", "reportedLine": 2774, "builtSha256": "36fdc0c907284a098f35296097bb02a2df5278f9321aaf42a4c5623dbc40c6ec", "reportedSourceExcerpt": "\t\t\t\tnoResize = ts.getData( $header, tmp, 'resizable' ) === 'false';\n\t\t\t\tif ( !noResize ) {\n\t\t\t\t\t$( '