From b39ea7a647fafe20d780597a9c1aca06fc1e7010 Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Sun, 15 Mar 2026 14:48:14 +0100 Subject: [PATCH] fix: address bug review findings - Debounce MutationObserver (200ms) and scope to #modelGrid when available - Sanitize data.updated via parseInt before innerHTML to prevent XSS - Guard pagination against page_size=0 (division by zero) and negative values - Reject non-POST requests on /api/lm-extra/fetch-stats (state-mutating) - Early return in upsert_batch for empty rows list Co-Authored-By: Claude Opus 4.6 --- proxy.py | 6 ++++-- static/lm_stats_ui.js | 17 ++++++++++++----- stats_db.py | 2 ++ 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/proxy.py b/proxy.py index 6aeb5f4..af2eeea 100644 --- a/proxy.py +++ b/proxy.py @@ -284,8 +284,8 @@ async def _proxy_list_with_stats_sort( """Fetch all items from remote, sort by stats locally, paginate.""" from urllib.parse import urlencode - page = int(request.query.get("page", "1")) - page_size = int(request.query.get("page_size", "20")) + page = max(1, int(request.query.get("page", "1"))) + page_size = max(1, int(request.query.get("page_size", "20"))) sort_key, _, order = sort_by.partition(":") order = order or "desc" @@ -342,6 +342,8 @@ async def _proxy_list_with_stats_sort( async def _handle_fetch_stats(request: web.Request) -> web.Response: """Bulk fetch CivitAI stats for all models.""" + if request.method != "POST": + return web.json_response({"error": "POST required"}, status=405) try: from .stats_service import StatsFetchService except ImportError: diff --git a/static/lm_stats_ui.js b/static/lm_stats_ui.js index 2b9a40f..e946d23 100644 --- a/static/lm_stats_ui.js +++ b/static/lm_stats_ui.js @@ -174,7 +174,8 @@ const resp = await _origFetch("/api/lm-extra/fetch-stats", { method: "POST" }); const data = await resp.json(); if (data.success) { - btn.innerHTML = ` ${data.updated} updated`; + const count = parseInt(data.updated, 10) || 0; + btn.innerHTML = ` ${count} updated`; setTimeout(() => { btn.innerHTML = ' Fetch Stats'; btn.disabled = false; @@ -205,13 +206,19 @@ addFetchStatsButton(); patchCards(); + let debounceTimer = null; const observer = new MutationObserver(() => { - patchCards(); - patchSortDropdown(); - addFetchStatsButton(); + if (debounceTimer) return; + debounceTimer = setTimeout(() => { + debounceTimer = null; + patchCards(); + patchSortDropdown(); + addFetchStatsButton(); + }, 200); }); - observer.observe(document.body, { childList: true, subtree: true }); + const grid = document.getElementById("modelGrid"); + observer.observe(grid || document.body, { childList: true, subtree: true }); } // ── Init ─────────────────────────────────────────────────────── diff --git a/stats_db.py b/stats_db.py index 6060295..6201fc6 100644 --- a/stats_db.py +++ b/stats_db.py @@ -74,6 +74,8 @@ class StatsDB: def upsert_batch(self, rows: list[tuple[str, dict]]) -> None: """Insert or update stats for multiple models.""" + if not rows: + return conn = self._ensure_conn() now = time.time() conn.executemany(