From 98d4b3b55b38de38277398db89a6edd593029764 Mon Sep 17 00:00:00 2001 From: Chuck Date: Sat, 30 May 2026 19:59:57 -0400 Subject: [PATCH] fix(security): address CodeQL and coderabbit review findings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Security fixes ### pages_v3.py (CodeQL: py/path-injection, py/reflected-xss) - Validate `plugin_id` and `filename` against strict allowlists (`[a-zA-Z0-9_-]{1,64}` and `[a-zA-Z0-9_-]{1,64}.html`) before any path or script operations — satisfies CodeQL path-injection checks - Error responses returned as `text/plain` with no user data in body - HTML-meta-char escaping on PLUGIN_ID value in script tag (defence in depth) ### array-table.js (CodeQL: js/prototype-pollution) - Guard `setNestedValue()` against `__proto__`, `prototype`, and `constructor` keys; silently drops any write targeting those keys ### plugin-file-manager.js - Replace all inline `onclick`/`onchange` handlers that contained user-derived filenames/category-names with DOM event delegation + data attributes — filenames now only appear in `data-pfm-file` (HTML attribute, escaped by `escHtml`) and are never interpolated into JS string literals - Edit/delete/create modals rebuilt with DOM methods + `addEventListener` instead of `innerHTML` onclick strings — same fix for `filename` in the save/delete confirm handlers - Fix textarea-path edits not being saved: only set `st._editData` for the tabular code path; leave it null for the textarea path so `_pfmSave()` reads ` -
`; +
`; } }; @@ -401,14 +431,23 @@ st._tableCols = cols; } + // Store buildPage in per-instance state so multiple instances don't + // clobber each other's pagination via a shared global. + st._buildPage = buildPage; buildPage(st._tablePage || 1); - window._pfmTablePage = function (fId, p) { - const s = getState(fId); - const totalP = Math.ceil(s._tableEntries.length / s.entriesPerPage); - buildPage(Math.max(1, Math.min(p, totalP))); - }; } + // Global dispatcher — resolves the per-instance buildPage from state so + // multiple plugin-file-manager instances don't clobber each other. + window._pfmTablePage = function (fId, p) { + const s = getState(fId); + if (s._buildPage) { + const total = s._tableEntries ? s._tableEntries.length : 0; + const totalP = Math.ceil(total / s.entriesPerPage) || 1; + s._buildPage(Math.max(1, Math.min(p, totalP))); + } + }; + window._pfmCellEdit = function (fieldId, day, col, value) { const st = getState(fieldId); if (st._editData && st._editData[day]) st._editData[day][col] = value; @@ -454,30 +493,32 @@ window._pfmOpenDelete = function (fieldId, filename) { const overlay = createOverlay(fieldId); - overlay.innerHTML = ` -
-
- Delete File - -
-
-
- ${escHtml(filename)} will be permanently deleted and removed - from the plugin configuration. This cannot be undone. -
-
-