diff --git a/src/dashboard/templates/mobile_test.html b/src/dashboard/templates/mobile_test.html index a92c9f8..e0662ae 100644 --- a/src/dashboard/templates/mobile_test.html +++ b/src/dashboard/templates/mobile_test.html @@ -329,16 +329,21 @@ const failed = Object.values(results).filter(v => v === "fail").length; const skipped = Object.values(results).filter(v => v === "skip").length; const decided = passed + failed + skipped; + const summaryBody = document.getElementById("summary-body"); if (decided < TOTAL) { - document.getElementById("summary-body").innerHTML = - '
' + (TOTAL - decided) + ' scenario(s) still pending.
'; + summaryBody.innerHTML = ''; + const p = document.createElement('p'); + p.className = 'mt-summary-hint'; + p.textContent = (TOTAL - decided) + ' scenario(s) still pending.'; + summaryBody.appendChild(p); return; } const pct = TOTAL ? Math.round((passed / TOTAL) * 100) : 0; const color = failed > 0 ? "var(--red)" : "var(--green)"; - document.getElementById("summary-body").innerHTML = ` + + summaryBody.innerHTML = `⚠ ' + failed + ' failure(s) need attention before release.
' : 'All tested scenarios passed — ship it.
'} `; + + const statusMsg = document.createElement('p'); + statusMsg.style.marginTop = '12px'; + statusMsg.style.fontSize = '11px'; + if (failed > 0) { + statusMsg.style.color = 'var(--red)'; + statusMsg.textContent = '⚠ ' + failed + ' failure(s) need attention before release.'; + } else { + statusMsg.style.color = 'var(--green)'; + statusMsg.textContent = 'All tested scenarios passed — ship it.'; + } + summaryBody.appendChild(statusMsg); } function resetAll() { @@ -359,8 +375,13 @@ applyState(id, null); }); updateScore(results); - document.getElementById("summary-body").innerHTML = - 'Mark all scenarios above to see your final score.
'; + + const summaryBody = document.getElementById("summary-body"); + summaryBody.innerHTML = ''; + const p = document.createElement('p'); + p.className = 'mt-summary-hint'; + p.textContent = 'Mark all scenarios above to see your final score.'; + summaryBody.appendChild(p); } // Restore saved state on load diff --git a/tests/test_xss_prevention.py b/tests/test_xss_prevention.py new file mode 100644 index 0000000..f1d6549 --- /dev/null +++ b/tests/test_xss_prevention.py @@ -0,0 +1,25 @@ +"""Regression tests for XSS prevention in the dashboard.""" + +import pytest +from fastapi.testclient import TestClient + +def test_mobile_test_page_xss_prevention(client: TestClient): + """ + Verify that the mobile-test page uses safer DOM manipulation. + This test checks the template content for the presence of textContent + and proper usage of innerHTML for known safe constants. + """ + response = client.get("/mobile-test") + assert response.status_code == 200 + content = response.text + + # Check that we are using textContent for dynamic content + assert "textContent =" in content + + # Check that we've updated the summaryBody.innerHTML usage to be safer + # or replaced with appendChild/textContent where appropriate. + # The fix uses innerHTML with template literals for structural parts + # but textContent for data parts. + assert "summaryBody.innerHTML = '';" in content + assert "p.textContent =" in content + assert "statusMsg.textContent =" in content