From 4878f9b9dc3183a39e7d3b2f3acc427ae72fc8e9 Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Fri, 15 May 2026 08:34:30 -0700 Subject: [PATCH 1/3] fix: escape YAML data in threat model detail overlay to prevent XSS The showThreatDetail() function in all four threat model pages (en, ja, ko, zh-cn) concatenates YAML-sourced threat data directly into innerHTML without sanitization. Because the trust page invites community contributions to the threat model via pull requests, a malicious PR modifying threats.yaml could inject arbitrary HTML/JS that executes when a visitor clicks any threat card. Add an escapeHtml() helper that encodes &, <, >, ", and ' and wrap all data fields (risk, atlas, description, attackVector, affected, mitigations, residualRisk, recommendations) and i18n label values before innerHTML insertion. Existing textContent assignments (detailId, detailTitle) are already safe and left unchanged. Signed-off-by: Sebastien Tardif --- src/pages/trust/ja/threatmodel.astro | 7 +++++-- src/pages/trust/ko/threatmodel.astro | 7 +++++-- src/pages/trust/threatmodel.astro | 7 +++++-- src/pages/trust/zh-cn/threatmodel.astro | 7 +++++-- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/pages/trust/ja/threatmodel.astro b/src/pages/trust/ja/threatmodel.astro index 03e1feca..780a4f0f 100644 --- a/src/pages/trust/ja/threatmodel.astro +++ b/src/pages/trust/ja/threatmodel.astro @@ -746,14 +746,17 @@ for (const b of trustBoundaries) { function getRiskColor(risk) { switch (risk) { case 'Critical': return '#ef4444'; case 'High': return '#f97316'; case 'Medium': return '#eab308'; case 'Low': return '#22c55e'; default: return '#6b7280'; } } + function escapeHtml(s) { + return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"').replace(/'/g,'''); + } function showThreatDetail(threatId) { var threat = threats.find(function(t) { return t.id === threatId; }); if (!threat) return; document.getElementById('detailId').textContent = threat.id + ' \u2022 ' + threat.tactic; document.getElementById('detailTitle').textContent = threat.name; var rc = getRiskColor(threat.risk); - document.getElementById('detailBadges').innerHTML = '' + threat.risk + ' ' + detailLabels.riskSuffix + '' + threat.atlas + ''; - document.getElementById('detailBody').innerHTML = '
' + detailLabels.description + '
' + threat.description + '
' + detailLabels.attackVector + '
' + threat.attackVector + '
' + detailLabels.affected + '
' + threat.affected + '
' + detailLabels.mitigations + '
' + threat.mitigations + '
' + detailLabels.residualRisk + '
' + threat.residualRisk + '
' + detailLabels.recommendations + '
' + threat.recommendations + '
'; + document.getElementById('detailBadges').innerHTML = '' + escapeHtml(threat.risk) + ' ' + escapeHtml(detailLabels.riskSuffix) + '' + escapeHtml(threat.atlas) + ''; + document.getElementById('detailBody').innerHTML = '
' + escapeHtml(detailLabels.description) + '
' + escapeHtml(threat.description) + '
' + escapeHtml(detailLabels.attackVector) + '
' + escapeHtml(threat.attackVector) + '
' + escapeHtml(detailLabels.affected) + '
' + escapeHtml(threat.affected) + '
' + escapeHtml(detailLabels.mitigations) + '
' + escapeHtml(threat.mitigations) + '
' + escapeHtml(detailLabels.residualRisk) + '
' + escapeHtml(threat.residualRisk) + '
' + escapeHtml(detailLabels.recommendations) + '
' + escapeHtml(threat.recommendations) + '
'; document.getElementById('detailOverlay').classList.add('active'); document.querySelectorAll('.threat-card').forEach(function(c) { c.classList.remove('selected'); }); var sel = document.querySelector('[data-threat-id="' + threatId + '"]'); diff --git a/src/pages/trust/ko/threatmodel.astro b/src/pages/trust/ko/threatmodel.astro index c06318aa..d2f4e7de 100644 --- a/src/pages/trust/ko/threatmodel.astro +++ b/src/pages/trust/ko/threatmodel.astro @@ -746,14 +746,17 @@ for (const b of trustBoundaries) { function getRiskColor(risk) { switch (risk) { case 'Critical': return '#ef4444'; case 'High': return '#f97316'; case 'Medium': return '#eab308'; case 'Low': return '#22c55e'; default: return '#6b7280'; } } + function escapeHtml(s) { + return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"').replace(/'/g,'''); + } function showThreatDetail(threatId) { var threat = threats.find(function(t) { return t.id === threatId; }); if (!threat) return; document.getElementById('detailId').textContent = threat.id + ' \u2022 ' + threat.tactic; document.getElementById('detailTitle').textContent = threat.name; var rc = getRiskColor(threat.risk); - document.getElementById('detailBadges').innerHTML = '' + threat.risk + ' ' + detailLabels.riskSuffix + '' + threat.atlas + ''; - document.getElementById('detailBody').innerHTML = '
' + detailLabels.description + '
' + threat.description + '
' + detailLabels.attackVector + '
' + threat.attackVector + '
' + detailLabels.affected + '
' + threat.affected + '
' + detailLabels.mitigations + '
' + threat.mitigations + '
' + detailLabels.residualRisk + '
' + threat.residualRisk + '
' + detailLabels.recommendations + '
' + threat.recommendations + '
'; + document.getElementById('detailBadges').innerHTML = '' + escapeHtml(threat.risk) + ' ' + escapeHtml(detailLabels.riskSuffix) + '' + escapeHtml(threat.atlas) + ''; + document.getElementById('detailBody').innerHTML = '
' + escapeHtml(detailLabels.description) + '
' + escapeHtml(threat.description) + '
' + escapeHtml(detailLabels.attackVector) + '
' + escapeHtml(threat.attackVector) + '
' + escapeHtml(detailLabels.affected) + '
' + escapeHtml(threat.affected) + '
' + escapeHtml(detailLabels.mitigations) + '
' + escapeHtml(threat.mitigations) + '
' + escapeHtml(detailLabels.residualRisk) + '
' + escapeHtml(threat.residualRisk) + '
' + escapeHtml(detailLabels.recommendations) + '
' + escapeHtml(threat.recommendations) + '
'; document.getElementById('detailOverlay').classList.add('active'); document.querySelectorAll('.threat-card').forEach(function(c) { c.classList.remove('selected'); }); var sel = document.querySelector('[data-threat-id="' + threatId + '"]'); diff --git a/src/pages/trust/threatmodel.astro b/src/pages/trust/threatmodel.astro index e99ce5b7..5db49845 100644 --- a/src/pages/trust/threatmodel.astro +++ b/src/pages/trust/threatmodel.astro @@ -745,14 +745,17 @@ for (const b of trustBoundaries) { function getRiskColor(risk) { switch (risk) { case 'Critical': return '#ef4444'; case 'High': return '#f97316'; case 'Medium': return '#eab308'; case 'Low': return '#22c55e'; default: return '#6b7280'; } } + function escapeHtml(s) { + return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"').replace(/'/g,'''); + } function showThreatDetail(threatId) { var threat = threats.find(function(t) { return t.id === threatId; }); if (!threat) return; document.getElementById('detailId').textContent = threat.id + ' \u2022 ' + threat.tactic; document.getElementById('detailTitle').textContent = threat.name; var rc = getRiskColor(threat.risk); - document.getElementById('detailBadges').innerHTML = '' + threat.risk + ' Risk' + threat.atlas + ''; - document.getElementById('detailBody').innerHTML = '
Description
' + threat.description + '
Attack Vector
' + threat.attackVector + '
Affected Components
' + threat.affected + '
Current Mitigations
' + threat.mitigations + '
Residual Risk
' + threat.residualRisk + '
Recommendations
' + threat.recommendations + '
'; + document.getElementById('detailBadges').innerHTML = '' + escapeHtml(threat.risk) + ' Risk' + escapeHtml(threat.atlas) + ''; + document.getElementById('detailBody').innerHTML = '
Description
' + escapeHtml(threat.description) + '
Attack Vector
' + escapeHtml(threat.attackVector) + '
Affected Components
' + escapeHtml(threat.affected) + '
Current Mitigations
' + escapeHtml(threat.mitigations) + '
Residual Risk
' + escapeHtml(threat.residualRisk) + '
Recommendations
' + escapeHtml(threat.recommendations) + '
'; document.getElementById('detailOverlay').classList.add('active'); document.querySelectorAll('.threat-card').forEach(function(c) { c.classList.remove('selected'); }); var sel = document.querySelector('[data-threat-id="' + threatId + '"]'); diff --git a/src/pages/trust/zh-cn/threatmodel.astro b/src/pages/trust/zh-cn/threatmodel.astro index 915bc91c..4a1319ab 100644 --- a/src/pages/trust/zh-cn/threatmodel.astro +++ b/src/pages/trust/zh-cn/threatmodel.astro @@ -746,14 +746,17 @@ for (const b of trustBoundaries) { function getRiskColor(risk) { switch (risk) { case 'Critical': return '#ef4444'; case 'High': return '#f97316'; case 'Medium': return '#eab308'; case 'Low': return '#22c55e'; default: return '#6b7280'; } } + function escapeHtml(s) { + return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"').replace(/'/g,'''); + } function showThreatDetail(threatId) { var threat = threats.find(function(t) { return t.id === threatId; }); if (!threat) return; document.getElementById('detailId').textContent = threat.id + ' \u2022 ' + threat.tactic; document.getElementById('detailTitle').textContent = threat.name; var rc = getRiskColor(threat.risk); - document.getElementById('detailBadges').innerHTML = '' + threat.risk + ' ' + detailLabels.riskSuffix + '' + threat.atlas + ''; - document.getElementById('detailBody').innerHTML = '
' + detailLabels.description + '
' + threat.description + '
' + detailLabels.attackVector + '
' + threat.attackVector + '
' + detailLabels.affected + '
' + threat.affected + '
' + detailLabels.mitigations + '
' + threat.mitigations + '
' + detailLabels.residualRisk + '
' + threat.residualRisk + '
' + detailLabels.recommendations + '
' + threat.recommendations + '
'; + document.getElementById('detailBadges').innerHTML = '' + escapeHtml(threat.risk) + ' ' + escapeHtml(detailLabels.riskSuffix) + '' + escapeHtml(threat.atlas) + ''; + document.getElementById('detailBody').innerHTML = '
' + escapeHtml(detailLabels.description) + '
' + escapeHtml(threat.description) + '
' + escapeHtml(detailLabels.attackVector) + '
' + escapeHtml(threat.attackVector) + '
' + escapeHtml(detailLabels.affected) + '
' + escapeHtml(threat.affected) + '
' + escapeHtml(detailLabels.mitigations) + '
' + escapeHtml(threat.mitigations) + '
' + escapeHtml(detailLabels.residualRisk) + '
' + escapeHtml(threat.residualRisk) + '
' + escapeHtml(detailLabels.recommendations) + '
' + escapeHtml(threat.recommendations) + '
'; document.getElementById('detailOverlay').classList.add('active'); document.querySelectorAll('.threat-card').forEach(function(c) { c.classList.remove('selected'); }); var sel = document.querySelector('[data-threat-id="' + threatId + '"]'); From 5638ca0bff25a6deef98eb92f7659f52c70b08f0 Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Fri, 15 May 2026 11:24:55 -0700 Subject: [PATCH 2/3] fix: use DOM API for badges to prevent href attribute XSS Replace innerHTML badge construction with createElement/setAttribute to address VADE security review feedback. escapeHtml() is insufficient for href attributes because the browser decodes HTML entities during innerHTML parsing, allowing attribute boundary breakout. Using setAttribute bypasses HTML parsing entirely. All four locale files updated (en, ja, ko, zh-cn). Signed-off-by: Sebastien Tardif --- src/pages/trust/ja/threatmodel.astro | 16 +++++++++++++++- src/pages/trust/ko/threatmodel.astro | 16 +++++++++++++++- src/pages/trust/threatmodel.astro | 16 +++++++++++++++- src/pages/trust/zh-cn/threatmodel.astro | 16 +++++++++++++++- 4 files changed, 60 insertions(+), 4 deletions(-) diff --git a/src/pages/trust/ja/threatmodel.astro b/src/pages/trust/ja/threatmodel.astro index 780a4f0f..53a8a151 100644 --- a/src/pages/trust/ja/threatmodel.astro +++ b/src/pages/trust/ja/threatmodel.astro @@ -755,7 +755,21 @@ for (const b of trustBoundaries) { document.getElementById('detailId').textContent = threat.id + ' \u2022 ' + threat.tactic; document.getElementById('detailTitle').textContent = threat.name; var rc = getRiskColor(threat.risk); - document.getElementById('detailBadges').innerHTML = '' + escapeHtml(threat.risk) + ' ' + escapeHtml(detailLabels.riskSuffix) + '' + escapeHtml(threat.atlas) + ''; + var badgesDiv = document.getElementById('detailBadges'); + badgesDiv.innerHTML = ''; + var riskSpan = document.createElement('span'); + riskSpan.className = 'detail-badge'; + riskSpan.style.background = rc + '12'; + riskSpan.style.color = rc; + riskSpan.textContent = threat.risk + ' ' + detailLabels.riskSuffix; + badgesDiv.appendChild(riskSpan); + var atlasLink = document.createElement('a'); + atlasLink.className = 'detail-badge atlas-link'; + atlasLink.style.background = 'rgba(255,90,54,0.08)'; + atlasLink.setAttribute('href', 'https://atlas.mitre.org/techniques/' + threat.atlas); + atlasLink.setAttribute('target', '_blank'); + atlasLink.textContent = threat.atlas; + badgesDiv.appendChild(atlasLink); document.getElementById('detailBody').innerHTML = '
' + escapeHtml(detailLabels.description) + '
' + escapeHtml(threat.description) + '
' + escapeHtml(detailLabels.attackVector) + '
' + escapeHtml(threat.attackVector) + '
' + escapeHtml(detailLabels.affected) + '
' + escapeHtml(threat.affected) + '
' + escapeHtml(detailLabels.mitigations) + '
' + escapeHtml(threat.mitigations) + '
' + escapeHtml(detailLabels.residualRisk) + '
' + escapeHtml(threat.residualRisk) + '
' + escapeHtml(detailLabels.recommendations) + '
' + escapeHtml(threat.recommendations) + '
'; document.getElementById('detailOverlay').classList.add('active'); document.querySelectorAll('.threat-card').forEach(function(c) { c.classList.remove('selected'); }); diff --git a/src/pages/trust/ko/threatmodel.astro b/src/pages/trust/ko/threatmodel.astro index d2f4e7de..d670f950 100644 --- a/src/pages/trust/ko/threatmodel.astro +++ b/src/pages/trust/ko/threatmodel.astro @@ -755,7 +755,21 @@ for (const b of trustBoundaries) { document.getElementById('detailId').textContent = threat.id + ' \u2022 ' + threat.tactic; document.getElementById('detailTitle').textContent = threat.name; var rc = getRiskColor(threat.risk); - document.getElementById('detailBadges').innerHTML = '' + escapeHtml(threat.risk) + ' ' + escapeHtml(detailLabels.riskSuffix) + '' + escapeHtml(threat.atlas) + ''; + var badgesDiv = document.getElementById('detailBadges'); + badgesDiv.innerHTML = ''; + var riskSpan = document.createElement('span'); + riskSpan.className = 'detail-badge'; + riskSpan.style.background = rc + '12'; + riskSpan.style.color = rc; + riskSpan.textContent = threat.risk + ' ' + detailLabels.riskSuffix; + badgesDiv.appendChild(riskSpan); + var atlasLink = document.createElement('a'); + atlasLink.className = 'detail-badge atlas-link'; + atlasLink.style.background = 'rgba(255,90,54,0.08)'; + atlasLink.setAttribute('href', 'https://atlas.mitre.org/techniques/' + threat.atlas); + atlasLink.setAttribute('target', '_blank'); + atlasLink.textContent = threat.atlas; + badgesDiv.appendChild(atlasLink); document.getElementById('detailBody').innerHTML = '
' + escapeHtml(detailLabels.description) + '
' + escapeHtml(threat.description) + '
' + escapeHtml(detailLabels.attackVector) + '
' + escapeHtml(threat.attackVector) + '
' + escapeHtml(detailLabels.affected) + '
' + escapeHtml(threat.affected) + '
' + escapeHtml(detailLabels.mitigations) + '
' + escapeHtml(threat.mitigations) + '
' + escapeHtml(detailLabels.residualRisk) + '
' + escapeHtml(threat.residualRisk) + '
' + escapeHtml(detailLabels.recommendations) + '
' + escapeHtml(threat.recommendations) + '
'; document.getElementById('detailOverlay').classList.add('active'); document.querySelectorAll('.threat-card').forEach(function(c) { c.classList.remove('selected'); }); diff --git a/src/pages/trust/threatmodel.astro b/src/pages/trust/threatmodel.astro index 5db49845..4a64a5ec 100644 --- a/src/pages/trust/threatmodel.astro +++ b/src/pages/trust/threatmodel.astro @@ -754,7 +754,21 @@ for (const b of trustBoundaries) { document.getElementById('detailId').textContent = threat.id + ' \u2022 ' + threat.tactic; document.getElementById('detailTitle').textContent = threat.name; var rc = getRiskColor(threat.risk); - document.getElementById('detailBadges').innerHTML = '' + escapeHtml(threat.risk) + ' Risk' + escapeHtml(threat.atlas) + ''; + var badgesDiv = document.getElementById('detailBadges'); + badgesDiv.innerHTML = ''; + var riskSpan = document.createElement('span'); + riskSpan.className = 'detail-badge'; + riskSpan.style.background = rc + '12'; + riskSpan.style.color = rc; + riskSpan.textContent = threat.risk + ' Risk'; + badgesDiv.appendChild(riskSpan); + var atlasLink = document.createElement('a'); + atlasLink.className = 'detail-badge atlas-link'; + atlasLink.style.background = 'rgba(255,90,54,0.08)'; + atlasLink.setAttribute('href', 'https://atlas.mitre.org/techniques/' + threat.atlas); + atlasLink.setAttribute('target', '_blank'); + atlasLink.textContent = threat.atlas; + badgesDiv.appendChild(atlasLink); document.getElementById('detailBody').innerHTML = '
Description
' + escapeHtml(threat.description) + '
Attack Vector
' + escapeHtml(threat.attackVector) + '
Affected Components
' + escapeHtml(threat.affected) + '
Current Mitigations
' + escapeHtml(threat.mitigations) + '
Residual Risk
' + escapeHtml(threat.residualRisk) + '
Recommendations
' + escapeHtml(threat.recommendations) + '
'; document.getElementById('detailOverlay').classList.add('active'); document.querySelectorAll('.threat-card').forEach(function(c) { c.classList.remove('selected'); }); diff --git a/src/pages/trust/zh-cn/threatmodel.astro b/src/pages/trust/zh-cn/threatmodel.astro index 4a1319ab..b369033a 100644 --- a/src/pages/trust/zh-cn/threatmodel.astro +++ b/src/pages/trust/zh-cn/threatmodel.astro @@ -755,7 +755,21 @@ for (const b of trustBoundaries) { document.getElementById('detailId').textContent = threat.id + ' \u2022 ' + threat.tactic; document.getElementById('detailTitle').textContent = threat.name; var rc = getRiskColor(threat.risk); - document.getElementById('detailBadges').innerHTML = '' + escapeHtml(threat.risk) + ' ' + escapeHtml(detailLabels.riskSuffix) + '' + escapeHtml(threat.atlas) + ''; + var badgesDiv = document.getElementById('detailBadges'); + badgesDiv.innerHTML = ''; + var riskSpan = document.createElement('span'); + riskSpan.className = 'detail-badge'; + riskSpan.style.background = rc + '12'; + riskSpan.style.color = rc; + riskSpan.textContent = threat.risk + ' ' + detailLabels.riskSuffix; + badgesDiv.appendChild(riskSpan); + var atlasLink = document.createElement('a'); + atlasLink.className = 'detail-badge atlas-link'; + atlasLink.style.background = 'rgba(255,90,54,0.08)'; + atlasLink.setAttribute('href', 'https://atlas.mitre.org/techniques/' + threat.atlas); + atlasLink.setAttribute('target', '_blank'); + atlasLink.textContent = threat.atlas; + badgesDiv.appendChild(atlasLink); document.getElementById('detailBody').innerHTML = '
' + escapeHtml(detailLabels.description) + '
' + escapeHtml(threat.description) + '
' + escapeHtml(detailLabels.attackVector) + '
' + escapeHtml(threat.attackVector) + '
' + escapeHtml(detailLabels.affected) + '
' + escapeHtml(threat.affected) + '
' + escapeHtml(detailLabels.mitigations) + '
' + escapeHtml(threat.mitigations) + '
' + escapeHtml(detailLabels.residualRisk) + '
' + escapeHtml(threat.residualRisk) + '
' + escapeHtml(detailLabels.recommendations) + '
' + escapeHtml(threat.recommendations) + '
'; document.getElementById('detailOverlay').classList.add('active'); document.querySelectorAll('.threat-card').forEach(function(c) { c.classList.remove('selected'); }); From ea636d1219b55f808089ba40ea3fccd8bd219e20 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 15 May 2026 22:34:18 +0100 Subject: [PATCH 3/3] fix: harden threat model overlay rendering --- src/pages/trust/ja/threatmodel.astro | 27 ++++++++++++++++++++----- src/pages/trust/ko/threatmodel.astro | 27 ++++++++++++++++++++----- src/pages/trust/threatmodel.astro | 27 ++++++++++++++++++++----- src/pages/trust/zh-cn/threatmodel.astro | 27 ++++++++++++++++++++----- 4 files changed, 88 insertions(+), 20 deletions(-) diff --git a/src/pages/trust/ja/threatmodel.astro b/src/pages/trust/ja/threatmodel.astro index 53a8a151..15f7dbfb 100644 --- a/src/pages/trust/ja/threatmodel.astro +++ b/src/pages/trust/ja/threatmodel.astro @@ -746,8 +746,18 @@ for (const b of trustBoundaries) { function getRiskColor(risk) { switch (risk) { case 'Critical': return '#ef4444'; case 'High': return '#f97316'; case 'Medium': return '#eab308'; case 'Low': return '#22c55e'; default: return '#6b7280'; } } - function escapeHtml(s) { - return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"').replace(/'/g,'''); + function appendDetailSection(parent, label, value) { + var section = document.createElement('div'); + section.className = 'detail-section'; + var labelDiv = document.createElement('div'); + labelDiv.className = 'detail-label'; + labelDiv.textContent = label; + var valueDiv = document.createElement('div'); + valueDiv.className = 'detail-value'; + valueDiv.textContent = value == null ? '' : String(value); + section.appendChild(labelDiv); + section.appendChild(valueDiv); + parent.appendChild(section); } function showThreatDetail(threatId) { var threat = threats.find(function(t) { return t.id === threatId; }); @@ -756,7 +766,7 @@ for (const b of trustBoundaries) { document.getElementById('detailTitle').textContent = threat.name; var rc = getRiskColor(threat.risk); var badgesDiv = document.getElementById('detailBadges'); - badgesDiv.innerHTML = ''; + badgesDiv.textContent = ''; var riskSpan = document.createElement('span'); riskSpan.className = 'detail-badge'; riskSpan.style.background = rc + '12'; @@ -770,10 +780,17 @@ for (const b of trustBoundaries) { atlasLink.setAttribute('target', '_blank'); atlasLink.textContent = threat.atlas; badgesDiv.appendChild(atlasLink); - document.getElementById('detailBody').innerHTML = '
' + escapeHtml(detailLabels.description) + '
' + escapeHtml(threat.description) + '
' + escapeHtml(detailLabels.attackVector) + '
' + escapeHtml(threat.attackVector) + '
' + escapeHtml(detailLabels.affected) + '
' + escapeHtml(threat.affected) + '
' + escapeHtml(detailLabels.mitigations) + '
' + escapeHtml(threat.mitigations) + '
' + escapeHtml(detailLabels.residualRisk) + '
' + escapeHtml(threat.residualRisk) + '
' + escapeHtml(detailLabels.recommendations) + '
' + escapeHtml(threat.recommendations) + '
'; + var detailBody = document.getElementById('detailBody'); + detailBody.textContent = ''; + appendDetailSection(detailBody, detailLabels.description, threat.description); + appendDetailSection(detailBody, detailLabels.attackVector, threat.attackVector); + appendDetailSection(detailBody, detailLabels.affected, threat.affected); + appendDetailSection(detailBody, detailLabels.mitigations, threat.mitigations); + appendDetailSection(detailBody, detailLabels.residualRisk, threat.residualRisk); + appendDetailSection(detailBody, detailLabels.recommendations, threat.recommendations); document.getElementById('detailOverlay').classList.add('active'); document.querySelectorAll('.threat-card').forEach(function(c) { c.classList.remove('selected'); }); - var sel = document.querySelector('[data-threat-id="' + threatId + '"]'); + var sel = Array.prototype.find.call(document.querySelectorAll('.threat-card'), function(c) { return c.dataset.threatId === threatId; }); if (sel) sel.classList.add('selected'); } function closeDetail() { document.getElementById('detailOverlay').classList.remove('active'); document.querySelectorAll('.threat-card').forEach(function(c) { c.classList.remove('selected'); }); } diff --git a/src/pages/trust/ko/threatmodel.astro b/src/pages/trust/ko/threatmodel.astro index d670f950..77b0e448 100644 --- a/src/pages/trust/ko/threatmodel.astro +++ b/src/pages/trust/ko/threatmodel.astro @@ -746,8 +746,18 @@ for (const b of trustBoundaries) { function getRiskColor(risk) { switch (risk) { case 'Critical': return '#ef4444'; case 'High': return '#f97316'; case 'Medium': return '#eab308'; case 'Low': return '#22c55e'; default: return '#6b7280'; } } - function escapeHtml(s) { - return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"').replace(/'/g,'''); + function appendDetailSection(parent, label, value) { + var section = document.createElement('div'); + section.className = 'detail-section'; + var labelDiv = document.createElement('div'); + labelDiv.className = 'detail-label'; + labelDiv.textContent = label; + var valueDiv = document.createElement('div'); + valueDiv.className = 'detail-value'; + valueDiv.textContent = value == null ? '' : String(value); + section.appendChild(labelDiv); + section.appendChild(valueDiv); + parent.appendChild(section); } function showThreatDetail(threatId) { var threat = threats.find(function(t) { return t.id === threatId; }); @@ -756,7 +766,7 @@ for (const b of trustBoundaries) { document.getElementById('detailTitle').textContent = threat.name; var rc = getRiskColor(threat.risk); var badgesDiv = document.getElementById('detailBadges'); - badgesDiv.innerHTML = ''; + badgesDiv.textContent = ''; var riskSpan = document.createElement('span'); riskSpan.className = 'detail-badge'; riskSpan.style.background = rc + '12'; @@ -770,10 +780,17 @@ for (const b of trustBoundaries) { atlasLink.setAttribute('target', '_blank'); atlasLink.textContent = threat.atlas; badgesDiv.appendChild(atlasLink); - document.getElementById('detailBody').innerHTML = '
' + escapeHtml(detailLabels.description) + '
' + escapeHtml(threat.description) + '
' + escapeHtml(detailLabels.attackVector) + '
' + escapeHtml(threat.attackVector) + '
' + escapeHtml(detailLabels.affected) + '
' + escapeHtml(threat.affected) + '
' + escapeHtml(detailLabels.mitigations) + '
' + escapeHtml(threat.mitigations) + '
' + escapeHtml(detailLabels.residualRisk) + '
' + escapeHtml(threat.residualRisk) + '
' + escapeHtml(detailLabels.recommendations) + '
' + escapeHtml(threat.recommendations) + '
'; + var detailBody = document.getElementById('detailBody'); + detailBody.textContent = ''; + appendDetailSection(detailBody, detailLabels.description, threat.description); + appendDetailSection(detailBody, detailLabels.attackVector, threat.attackVector); + appendDetailSection(detailBody, detailLabels.affected, threat.affected); + appendDetailSection(detailBody, detailLabels.mitigations, threat.mitigations); + appendDetailSection(detailBody, detailLabels.residualRisk, threat.residualRisk); + appendDetailSection(detailBody, detailLabels.recommendations, threat.recommendations); document.getElementById('detailOverlay').classList.add('active'); document.querySelectorAll('.threat-card').forEach(function(c) { c.classList.remove('selected'); }); - var sel = document.querySelector('[data-threat-id="' + threatId + '"]'); + var sel = Array.prototype.find.call(document.querySelectorAll('.threat-card'), function(c) { return c.dataset.threatId === threatId; }); if (sel) sel.classList.add('selected'); } function closeDetail() { document.getElementById('detailOverlay').classList.remove('active'); document.querySelectorAll('.threat-card').forEach(function(c) { c.classList.remove('selected'); }); } diff --git a/src/pages/trust/threatmodel.astro b/src/pages/trust/threatmodel.astro index 4a64a5ec..1f933f1d 100644 --- a/src/pages/trust/threatmodel.astro +++ b/src/pages/trust/threatmodel.astro @@ -745,8 +745,18 @@ for (const b of trustBoundaries) { function getRiskColor(risk) { switch (risk) { case 'Critical': return '#ef4444'; case 'High': return '#f97316'; case 'Medium': return '#eab308'; case 'Low': return '#22c55e'; default: return '#6b7280'; } } - function escapeHtml(s) { - return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"').replace(/'/g,'''); + function appendDetailSection(parent, label, value) { + var section = document.createElement('div'); + section.className = 'detail-section'; + var labelDiv = document.createElement('div'); + labelDiv.className = 'detail-label'; + labelDiv.textContent = label; + var valueDiv = document.createElement('div'); + valueDiv.className = 'detail-value'; + valueDiv.textContent = value == null ? '' : String(value); + section.appendChild(labelDiv); + section.appendChild(valueDiv); + parent.appendChild(section); } function showThreatDetail(threatId) { var threat = threats.find(function(t) { return t.id === threatId; }); @@ -755,7 +765,7 @@ for (const b of trustBoundaries) { document.getElementById('detailTitle').textContent = threat.name; var rc = getRiskColor(threat.risk); var badgesDiv = document.getElementById('detailBadges'); - badgesDiv.innerHTML = ''; + badgesDiv.textContent = ''; var riskSpan = document.createElement('span'); riskSpan.className = 'detail-badge'; riskSpan.style.background = rc + '12'; @@ -769,10 +779,17 @@ for (const b of trustBoundaries) { atlasLink.setAttribute('target', '_blank'); atlasLink.textContent = threat.atlas; badgesDiv.appendChild(atlasLink); - document.getElementById('detailBody').innerHTML = '
Description
' + escapeHtml(threat.description) + '
Attack Vector
' + escapeHtml(threat.attackVector) + '
Affected Components
' + escapeHtml(threat.affected) + '
Current Mitigations
' + escapeHtml(threat.mitigations) + '
Residual Risk
' + escapeHtml(threat.residualRisk) + '
Recommendations
' + escapeHtml(threat.recommendations) + '
'; + var detailBody = document.getElementById('detailBody'); + detailBody.textContent = ''; + appendDetailSection(detailBody, 'Description', threat.description); + appendDetailSection(detailBody, 'Attack Vector', threat.attackVector); + appendDetailSection(detailBody, 'Affected Components', threat.affected); + appendDetailSection(detailBody, 'Current Mitigations', threat.mitigations); + appendDetailSection(detailBody, 'Residual Risk', threat.residualRisk); + appendDetailSection(detailBody, 'Recommendations', threat.recommendations); document.getElementById('detailOverlay').classList.add('active'); document.querySelectorAll('.threat-card').forEach(function(c) { c.classList.remove('selected'); }); - var sel = document.querySelector('[data-threat-id="' + threatId + '"]'); + var sel = Array.prototype.find.call(document.querySelectorAll('.threat-card'), function(c) { return c.dataset.threatId === threatId; }); if (sel) sel.classList.add('selected'); } function closeDetail() { document.getElementById('detailOverlay').classList.remove('active'); document.querySelectorAll('.threat-card').forEach(function(c) { c.classList.remove('selected'); }); } diff --git a/src/pages/trust/zh-cn/threatmodel.astro b/src/pages/trust/zh-cn/threatmodel.astro index b369033a..4edc2202 100644 --- a/src/pages/trust/zh-cn/threatmodel.astro +++ b/src/pages/trust/zh-cn/threatmodel.astro @@ -746,8 +746,18 @@ for (const b of trustBoundaries) { function getRiskColor(risk) { switch (risk) { case 'Critical': return '#ef4444'; case 'High': return '#f97316'; case 'Medium': return '#eab308'; case 'Low': return '#22c55e'; default: return '#6b7280'; } } - function escapeHtml(s) { - return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"').replace(/'/g,'''); + function appendDetailSection(parent, label, value) { + var section = document.createElement('div'); + section.className = 'detail-section'; + var labelDiv = document.createElement('div'); + labelDiv.className = 'detail-label'; + labelDiv.textContent = label; + var valueDiv = document.createElement('div'); + valueDiv.className = 'detail-value'; + valueDiv.textContent = value == null ? '' : String(value); + section.appendChild(labelDiv); + section.appendChild(valueDiv); + parent.appendChild(section); } function showThreatDetail(threatId) { var threat = threats.find(function(t) { return t.id === threatId; }); @@ -756,7 +766,7 @@ for (const b of trustBoundaries) { document.getElementById('detailTitle').textContent = threat.name; var rc = getRiskColor(threat.risk); var badgesDiv = document.getElementById('detailBadges'); - badgesDiv.innerHTML = ''; + badgesDiv.textContent = ''; var riskSpan = document.createElement('span'); riskSpan.className = 'detail-badge'; riskSpan.style.background = rc + '12'; @@ -770,10 +780,17 @@ for (const b of trustBoundaries) { atlasLink.setAttribute('target', '_blank'); atlasLink.textContent = threat.atlas; badgesDiv.appendChild(atlasLink); - document.getElementById('detailBody').innerHTML = '
' + escapeHtml(detailLabels.description) + '
' + escapeHtml(threat.description) + '
' + escapeHtml(detailLabels.attackVector) + '
' + escapeHtml(threat.attackVector) + '
' + escapeHtml(detailLabels.affected) + '
' + escapeHtml(threat.affected) + '
' + escapeHtml(detailLabels.mitigations) + '
' + escapeHtml(threat.mitigations) + '
' + escapeHtml(detailLabels.residualRisk) + '
' + escapeHtml(threat.residualRisk) + '
' + escapeHtml(detailLabels.recommendations) + '
' + escapeHtml(threat.recommendations) + '
'; + var detailBody = document.getElementById('detailBody'); + detailBody.textContent = ''; + appendDetailSection(detailBody, detailLabels.description, threat.description); + appendDetailSection(detailBody, detailLabels.attackVector, threat.attackVector); + appendDetailSection(detailBody, detailLabels.affected, threat.affected); + appendDetailSection(detailBody, detailLabels.mitigations, threat.mitigations); + appendDetailSection(detailBody, detailLabels.residualRisk, threat.residualRisk); + appendDetailSection(detailBody, detailLabels.recommendations, threat.recommendations); document.getElementById('detailOverlay').classList.add('active'); document.querySelectorAll('.threat-card').forEach(function(c) { c.classList.remove('selected'); }); - var sel = document.querySelector('[data-threat-id="' + threatId + '"]'); + var sel = Array.prototype.find.call(document.querySelectorAll('.threat-card'), function(c) { return c.dataset.threatId === threatId; }); if (sel) sel.classList.add('selected'); } function closeDetail() { document.getElementById('detailOverlay').classList.remove('active'); document.querySelectorAll('.threat-card').forEach(function(c) { c.classList.remove('selected'); }); }