From 228da83664275bc53cb22141665957f4b390f9a1 Mon Sep 17 00:00:00 2001 From: Thormatt Date: Fri, 12 Jun 2026 16:42:46 -0400 Subject: [PATCH] fix(site): close DOM-XSS in the deployed trace.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The verification artifact's trace.js read claim titles back via .textContent (entities decoded) and re-injected them through innerHTML when building the ledger and verdict pill — re-opening the XSS the server-side escaping closes. The rendering asset copy was fixed in PR #10; the site/ source-of-truth original was missed. Build the row/pill with createElement + textContent only. Also gitignore the .playwright-mcp browser-verification artifacts. Co-Authored-By: Claude Fable 5 --- .gitignore | 3 +++ site/trace.js | 19 +++++++++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index c513b1e..a2c88a8 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,6 @@ benchmarks/*/results/ # Local-only benchmark data: third-party datasets we re-fetch via bootstrap scripts benchmarks/faithfulness/*.jsonl # HHEM model cache lives under HF_HOME by default; not in this repo. + +# Playwright MCP browser-verification artifacts +.playwright-mcp/ diff --git a/site/trace.js b/site/trace.js index 9f85548..788a65d 100644 --- a/site/trace.js +++ b/site/trace.js @@ -31,7 +31,10 @@ function resolveVerdicts() { pill.classList.remove('pending'); pill.classList.add(v.cls); const scoreTxt = score ? ` · ${score}` : ''; - pill.innerHTML = `${v.label}${scoreTxt}`; + const vt = document.createElement('span'); + vt.className = 'vt'; + vt.textContent = `${v.label}${scoreTxt}`; + pill.replaceChildren(vt); }); } @@ -72,11 +75,15 @@ function buildLedger(claims) { li.href = `#${c.id}`; li.className = `led ${c.kind}`; const v = VERDICTS[c.kind]; - li.innerHTML = ` - ${v.glyph} - ${c.n} - ${c.title} - `; + // c.title came from .textContent (entities already decoded) — injecting + // it through innerHTML would re-open the XSS the server-side escaping + // closed. Build the row with createElement/textContent only. + for (const [cls, text] of [["glyph", v.glyph], ["cid", c.n], ["lbl", c.title]]) { + const span = document.createElement('span'); + span.className = cls; + span.textContent = text; + li.appendChild(span); + } list.appendChild(li); }); const total = document.getElementById('led-total');