From 91989a021641a6bbee64b28a10e2fb94c79307d0 Mon Sep 17 00:00:00 2001 From: thatsatechnique <28403172+thatsatechnique@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:21:14 -0800 Subject: [PATCH] =?UTF-8?q?fix(ook):=20address=20Copilot=20review=20?= =?UTF-8?q?=E2=80=94=20stale=20process,=20XSS=20presets,=20localStorage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Detect crashed rtl_433 process via poll() and clean up stale state instead of permanently blocking restarts with 409 - Replace innerHTML+onclick preset rendering with createElement/addEventListener to prevent XSS via crafted localStorage frequency values - Normalize preset frequencies to toFixed(3) on save and render - Add try/catch + shape validation to loadPresets() for corrupted localStorage Co-Authored-By: Claude Opus 4.6 --- routes/ook.py | 6 +++++- static/js/modes/ook.js | 40 +++++++++++++++++++++++++++++++--------- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/routes/ook.py b/routes/ook.py index ddefde8..b02769a 100644 --- a/routes/ook.py +++ b/routes/ook.py @@ -63,7 +63,11 @@ def start_ook() -> Response: with app_module.ook_lock: if app_module.ook_process: - return jsonify({'status': 'error', 'message': 'OOK decoder already running'}), 409 + # If the process exited/crashed, clean up stale state and allow restart + if app_module.ook_process.poll() is not None: + cleanup_ook(emit_status=False) + else: + return jsonify({'status': 'error', 'message': 'OOK decoder already running'}), 409 data = request.json or {} diff --git a/static/js/modes/ook.js b/static/js/modes/ook.js index 60100fd..4ad6f1b 100644 --- a/static/js/modes/ook.js +++ b/static/js/modes/ook.js @@ -414,7 +414,16 @@ var OokMode = (function () { function loadPresets() { var saved = localStorage.getItem('ookFreqPresets'); - return saved ? JSON.parse(saved) : DEFAULT_FREQ_PRESETS.slice(); + if (!saved) return DEFAULT_FREQ_PRESETS.slice(); + try { + var parsed = JSON.parse(saved); + if (Array.isArray(parsed) && parsed.every(function (v) { + return typeof v === 'string' && Number.isFinite(Number.parseFloat(v)); + })) { + return parsed; + } + } catch (_) {} + return DEFAULT_FREQ_PRESETS.slice(); } function savePresets(presets) { @@ -425,24 +434,37 @@ var OokMode = (function () { var container = document.getElementById('ookPresetButtons'); if (!container) return; var presets = loadPresets(); - container.innerHTML = presets.map(function (freq) { - return ''; - }).join(''); + container.textContent = ''; + presets.forEach(function (freq) { + var num = Number.parseFloat(freq); + if (!Number.isFinite(num)) return; + var normalized = num.toFixed(3); + var btn = document.createElement('button'); + btn.className = 'preset-btn'; + btn.title = 'Right-click to remove'; + btn.textContent = normalized; + btn.addEventListener('click', function () { OokMode.setFreq(normalized); }); + btn.addEventListener('contextmenu', function (e) { + e.preventDefault(); + OokMode.removePreset(normalized); + }); + container.appendChild(btn); + }); } function addPreset() { var input = document.getElementById('ookNewPresetFreq'); if (!input) return; var freq = input.value.trim(); - if (!freq || isNaN(parseFloat(freq))) { + var num = Number.parseFloat(freq); + if (!freq || !Number.isFinite(num)) { alert('Enter a valid frequency (MHz)'); return; } + var normalized = num.toFixed(3); var presets = loadPresets(); - if (presets.indexOf(freq) === -1) { - presets.push(freq); + if (presets.indexOf(normalized) === -1) { + presets.push(normalized); savePresets(presets); renderPresets(); }