mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-02 17:28:05 +00:00
Resolve remaining Codacy findings in settings search
- Validate plugin ids against a strict allowlist (mirroring the server's _SAFE_PLUGIN_ID_RE) before they can appear in a fetch path, so the request URL is never built from unvalidated input (Codacy: user-controlled URL). - Document that the fetched HTML is parsed into an inert document (scripts never run, never inserted into the live DOM) purely to read field text for the search index. - Declare block-scoped locals with const instead of var where they were nested inside conditionals (Codacy: var not at function root). No behavior change; re-verified with ESLint and the headless-Chromium test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014gZxznuxw8L92FUMBN3Nqz
This commit is contained in:
@@ -35,6 +35,11 @@
|
|||||||
|
|
||||||
var MAX_RESULTS = 25;
|
var MAX_RESULTS = 25;
|
||||||
|
|
||||||
|
// Plugin ids are constrained to this allowlist (mirrors the server's
|
||||||
|
// _SAFE_PLUGIN_ID_RE in pages_v3.py) before they can appear in a request
|
||||||
|
// path, so a fetched URL can never be influenced by untrusted input.
|
||||||
|
var PLUGIN_ID_RE = /^[a-zA-Z0-9_-]{1,64}$/;
|
||||||
|
|
||||||
function debounce(fn, ms) {
|
function debounce(fn, ms) {
|
||||||
var t;
|
var t;
|
||||||
return function () {
|
return function () {
|
||||||
@@ -88,10 +93,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function fetchAndScan(url, tab, tabLabel) {
|
function fetchAndScan(url, tab, tabLabel) {
|
||||||
|
// `url` is always one of our own same-origin settings partial paths
|
||||||
|
// (CORE_TABS literals or a plugin path built from an allowlisted id).
|
||||||
return fetch(url, { headers: { 'X-Requested-With': 'settings-search' } })
|
return fetch(url, { headers: { 'X-Requested-With': 'settings-search' } })
|
||||||
.then(function (r) { return r.ok ? r.text() : ''; })
|
.then(function (r) { return r.ok ? r.text() : ''; })
|
||||||
.then(function (html) {
|
.then(function (html) {
|
||||||
if (!html) return [];
|
if (!html) return [];
|
||||||
|
// Parsed into an inert document: scripts do not run and it is
|
||||||
|
// never inserted into the live DOM — we only read labels and
|
||||||
|
// tooltip text from it to build the search index.
|
||||||
var doc = new DOMParser().parseFromString(html, 'text/html');
|
var doc = new DOMParser().parseFromString(html, 'text/html');
|
||||||
return scanDoc(doc, tab, tabLabel);
|
return scanDoc(doc, tab, tabLabel);
|
||||||
})
|
})
|
||||||
@@ -107,9 +117,10 @@
|
|||||||
|
|
||||||
var plugins = (window.installedPlugins || []);
|
var plugins = (window.installedPlugins || []);
|
||||||
plugins.forEach(function (p) {
|
plugins.forEach(function (p) {
|
||||||
if (!p || !p.id) return;
|
// Skip ids that don't match the strict allowlist so the request
|
||||||
jobs.push(fetchAndScan('/v3/partials/plugin-config/' + encodeURIComponent(p.id),
|
// path is always built from safe, validated components.
|
||||||
p.id, p.name || p.id));
|
if (!p || !p.id || !PLUGIN_ID_RE.test(p.id)) return;
|
||||||
|
jobs.push(fetchAndScan('/v3/partials/plugin-config/' + p.id, p.id, p.name || p.id));
|
||||||
});
|
});
|
||||||
|
|
||||||
buildPromise = Promise.all(jobs).then(function (lists) {
|
buildPromise = Promise.all(jobs).then(function (lists) {
|
||||||
@@ -158,7 +169,7 @@
|
|||||||
var lastTab = null;
|
var lastTab = null;
|
||||||
results.forEach(function (r, i) {
|
results.forEach(function (r, i) {
|
||||||
if (r.tabLabel !== lastTab) {
|
if (r.tabLabel !== lastTab) {
|
||||||
var group = document.createElement('div');
|
const group = document.createElement('div');
|
||||||
group.className = 'ssr-group';
|
group.className = 'ssr-group';
|
||||||
group.textContent = r.tabLabel;
|
group.textContent = r.tabLabel;
|
||||||
resultsBox.appendChild(group);
|
resultsBox.appendChild(group);
|
||||||
@@ -329,7 +340,7 @@
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
highlight(Math.max(activeIndex - 1, 0));
|
highlight(Math.max(activeIndex - 1, 0));
|
||||||
} else if (e.key === 'Enter') {
|
} else if (e.key === 'Enter') {
|
||||||
var chosen = currentResults.at(activeIndex >= 0 ? activeIndex : 0);
|
const chosen = currentResults.at(activeIndex >= 0 ? activeIndex : 0);
|
||||||
if (chosen) {
|
if (chosen) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
navigateToSetting(chosen);
|
navigateToSetting(chosen);
|
||||||
@@ -345,8 +356,8 @@
|
|||||||
var opt = e.target.closest('.ssr-option');
|
var opt = e.target.closest('.ssr-option');
|
||||||
if (!opt) return;
|
if (!opt) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var idx = parseInt(opt.getAttribute('data-idx'), 10);
|
const idx = parseInt(opt.getAttribute('data-idx'), 10);
|
||||||
var chosen = currentResults.at(idx);
|
const chosen = currentResults.at(idx);
|
||||||
if (chosen) navigateToSetting(chosen);
|
if (chosen) navigateToSetting(chosen);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -405,9 +416,9 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Toggle the "no matches" note if the filter box provides one.
|
// Toggle the "no matches" note if the filter box provides one.
|
||||||
var wrap = scope.querySelector('.settings-filter-wrap');
|
const wrap = scope.querySelector('.settings-filter-wrap');
|
||||||
if (wrap) {
|
if (wrap) {
|
||||||
var empty = wrap.querySelector('.settings-filter-empty');
|
const empty = wrap.querySelector('.settings-filter-empty');
|
||||||
if (empty) empty.classList.toggle('hidden', !(terms.length && !anyVisible));
|
if (empty) empty.classList.toggle('hidden', !(terms.length && !anyVisible));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user