From d161affda569b5d83d08c3b61ff784b3892c2d9e Mon Sep 17 00:00:00 2001 From: Yusrizal Ahmad Date: Mon, 22 Jun 2026 05:42:14 +0000 Subject: [PATCH 1/2] fix(#7218): harden fossil record tooltip against XSS --- visualizations/fossil-record.html | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/visualizations/fossil-record.html b/visualizations/fossil-record.html index ccaf2c1f1..47b13068b 100644 --- a/visualizations/fossil-record.html +++ b/visualizations/fossil-record.html @@ -196,6 +196,12 @@

⛏ The Fossil Record — RustChain Attestation Archaeology

}; let currentRange = 'all'; +function escapeHtml(str) { + const div = document.createElement('div'); + div.appendChild(document.createTextNode(str)); + return div.innerHTML; +} + let visibleArchs = new Set(ARCHS.map(a => a.name)); function getFilteredData() { @@ -209,7 +215,7 @@

⛏ The Fossil Record — RustChain Attestation Archaeology

const item = document.createElement('div'); item.className = 'legend-item'; item.dataset.arch = a.name; - item.innerHTML = `
${a.name}`; + item.innerHTML = `
${escapeHtml(a.name)}`; item.addEventListener('click', () => { if (visibleArchs.has(a.name)) { if (visibleArchs.size > 1) visibleArchs.delete(a.name); @@ -385,9 +391,9 @@

⛏ The Fossil Record — RustChain Attestation Archaeology

const d = new Date(ep.ts); const timeStr = d.toLocaleString(); - let html = `
Epoch #${ep.index + 1} · ${timeStr}
`; + let html = `
Epoch #${ep.index + 1} · ${escapeHtml(timeStr)}
`; if (arch) { - html += `
${arch.name}
`; + html += `
${escapeHtml(arch.name)}
`; html += `
Attestations: ${ep.counts[hoverArch]}
`; } @@ -397,7 +403,7 @@

⛏ The Fossil Record — RustChain Attestation Archaeology

ARCHS.forEach((a, ai) => { if (!visibleArchs.has(a.name)) return; const pct = Math.round(ep.counts[ai] / total * 100); - html += `${a.name}: ${ep.counts[ai]} (${pct}%)  `; + html += `${escapeHtml(a.name)}: ${ep.counts[ai]} (${pct}%)  `; }); html += ``; html += `
Total: ${total}
`; From dcb3a4a8d4cdb6b66d3eeaddf5ae40d6a8cb6413 Mon Sep 17 00:00:00 2001 From: Yzgaming005 Date: Wed, 24 Jun 2026 00:36:53 +0000 Subject: [PATCH 2/2] fix(#7218): apply XSS escaping to actual sinks in fossils/index.html MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scottcjn review: the original PR patched visualizations/fossil-record.html where values are hardcoded/local. The real XSS sinks are in fossils/index.html: - sampleMiners.join(', ') into tooltip.html() — attacker-controlled miner_id - message into container.html() in showError() — attacker-controlled error Adds escapeHtml() helper and applies it to both sinks. --- fossils/index.html | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/fossils/index.html b/fossils/index.html index 0f454bfba..5baf3f4f3 100644 --- a/fossils/index.html +++ b/fossils/index.html @@ -824,6 +824,12 @@

📖 About The Fossil Record

}); } + function escapeHtml(str) { + const div = document.createElement('div'); + div.appendChild(document.createTextNode(str)); + return div.innerHTML; + } + function showTooltip(event, d) { const archConfig = ARCHITECTURES[d.arch] || { label: d.arch }; @@ -849,7 +855,7 @@

📖 About The Fossil Record

html += `
Sample Miners: - ${sampleMiners.join(', ')} + ${escapeHtml(sampleMiners.join(', '))}
`; } @@ -915,7 +921,7 @@

📖 About The Fossil Record

const container = d3.select('#visualization'); container.html(`
- Error loading data: ${message} + Error loading data: ${escapeHtml(message)}