From 6171c64b90d144ac2692dd66707dddb079b658e1 Mon Sep 17 00:00:00 2001 From: justin Date: Mon, 27 Apr 2026 22:34:08 -0500 Subject: [PATCH] Fix 8 bugs: XSS, race condition, null safety, form reset, pricing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. XSS: error messages use textContent by default, innerHTML only for controlled HTML (CORES link) via allowHtml flag 2. XSS: name search errors built with DOM API, not innerHTML 3. Race condition: concurrent FRN lookups cancel prior request via AbortController tracking 4. Null safety: DOM element guards with error logging 5. Null safety: check.detail uses || "" fallback, \n →
6. Quote form: auto-resets after 3 seconds on successful submit 7. Pricing: discount uses Math.round(total*15)/100 for cent precision 8. Future-proofing: parseFloat for prices instead of parseInt Co-Authored-By: Claude Opus 4.6 (1M context) --- .../pages/tools/fcc-compliance-check.astro | 52 +++++++++++++++---- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/site/src/pages/tools/fcc-compliance-check.astro b/site/src/pages/tools/fcc-compliance-check.astro index 1d80a8c..532b7aa 100644 --- a/site/src/pages/tools/fcc-compliance-check.astro +++ b/site/src/pages/tools/fcc-compliance-check.astro @@ -202,9 +202,17 @@ import Base from "../../layouts/Base.astro"; const errorMessage = document.getElementById("error-message"); const resultsEl = document.getElementById("results"); + // Guard against missing DOM elements + if (!nameInput || !frnInput || !loadingEl || !errorBox || !errorMessage || !resultsEl) { + console.error("[fcc-check] Required DOM elements missing"); + } + + // Race condition guard — cancel prior fetch when new one starts + let currentController = null; + // --- Name search --- - nameSearchBtn.addEventListener("click", runNameSearch); - nameInput.addEventListener("keydown", (e) => { if (e.key === "Enter") runNameSearch(); }); + nameSearchBtn?.addEventListener("click", runNameSearch); + nameInput?.addEventListener("keydown", (e) => { if (e.key === "Enter") runNameSearch(); }); async function runNameSearch() { const name = nameInput.value.trim(); @@ -258,7 +266,11 @@ import Base from "../../layouts/Base.astro"; }); }); } catch (err) { - nameResults.innerHTML = `

Search error: ${err.message}

`; + const errP = document.createElement("p"); + errP.className = "text-sm text-red-600"; + errP.textContent = "Search error: " + (err.message || "Unknown error"); + nameResults.innerHTML = ""; + nameResults.appendChild(errP); } } @@ -321,6 +333,15 @@ import Base from "../../layouts/Base.astro"; statusEl.textContent = "✓ Request submitted! We'll email you within 1 business day."; statusEl.className = "mt-2 text-xs text-green-700"; btn.textContent = "Sent ✓"; + // Reset form after 3 seconds + setTimeout(() => { + document.getElementById("quote-form")?.classList.add("hidden"); + document.getElementById("quote-name").value = ""; + document.getElementById("quote-email").value = ""; + btn.textContent = "Request Assessment"; + btn.disabled = false; + statusEl.classList.add("hidden"); + }, 3000); } else { throw new Error("Failed"); } @@ -360,13 +381,20 @@ import Base from "../../layouts/Base.astro"; errorBox.classList.add("hidden"); loadingEl.classList.remove("hidden"); + // Cancel any prior in-flight request + if (currentController) currentController.abort(); + const controller = new AbortController(); + currentController = controller; + try { - const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 30000); const res = await fetch(`${API}/api/v1/fcc/lookup?frn=${frn}`, { signal: controller.signal }); clearTimeout(timeout); + // Ignore if a newer request superseded this one + if (currentController !== controller) return; + if (!res.ok) { const body = await res.json().catch(() => ({})); if (res.status === 400) { @@ -376,7 +404,7 @@ import Base from "../../layouts/Base.astro"; } const data = await res.json(); if (!data.entity_name && !data.cores?.entity_name && !data.filer_499) { - showError('FRN ' + frn + ' was not found in any FCC database. Verify your number at FCC CORES.'); + showError('FRN ' + frn + ' was not found in any FCC database. Verify your number at FCC CORES.', true); return; } renderResults(data); @@ -391,8 +419,12 @@ import Base from "../../layouts/Base.astro"; } } - function showError(msg) { - errorMessage.innerHTML = msg; + function showError(msg, allowHtml) { + if (allowHtml) { + errorMessage.innerHTML = msg; + } else { + errorMessage.textContent = msg; + } errorBox.classList.remove("hidden"); } @@ -684,7 +716,7 @@ import Base from "../../layouts/Base.astro"; function attachF499Handlers() { card.querySelector(".f499-yes")?.addEventListener("click", () => { showF499Result( - `${check.detail}\n${eName} had telecom revenue — filing is required for each missed year.`, + `${check.detail || ""}
${eName} had telecom revenue — filing is required for each missed year.`, "red" ); }); @@ -808,7 +840,7 @@ import Base from "../../layouts/Base.astro"; boxes.forEach((cb) => { if (cb.checked) { - const p = parseInt(cb.dataset.price, 10); + const p = parseFloat(cb.dataset.price) || 0; selectedIds.push(cb.dataset.id); if (p > 0) { // Exclude dc_agent from discount calc @@ -825,7 +857,7 @@ import Base from "../../layouts/Base.astro"; const fmt = (v) => v.toFixed(2); if (pricedCount >= 2) { - const discount = Math.round(total * 0.15); + const discount = Math.round(total * 15) / 100; // Precise to cents const final_ = total - discount; discountHtml = `
Subtotal