Skip to content

Commit 5638ca0

Browse files
committed
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 <sebtardif@ncf.ca>
1 parent 4878f9b commit 5638ca0

4 files changed

Lines changed: 60 additions & 4 deletions

File tree

src/pages/trust/ja/threatmodel.astro

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -755,7 +755,21 @@ for (const b of trustBoundaries) {
755755
document.getElementById('detailId').textContent = threat.id + ' \u2022 ' + threat.tactic;
756756
document.getElementById('detailTitle').textContent = threat.name;
757757
var rc = getRiskColor(threat.risk);
758-
document.getElementById('detailBadges').innerHTML = '<span class="detail-badge" style="background:' + rc + '12;color:' + rc + '">' + escapeHtml(threat.risk) + ' ' + escapeHtml(detailLabels.riskSuffix) + '</span><a href="https://atlas.mitre.org/techniques/' + escapeHtml(threat.atlas) + '" target="_blank" class="detail-badge atlas-link" style="background:rgba(255,90,54,0.08)">' + escapeHtml(threat.atlas) + '</a>';
758+
var badgesDiv = document.getElementById('detailBadges');
759+
badgesDiv.innerHTML = '';
760+
var riskSpan = document.createElement('span');
761+
riskSpan.className = 'detail-badge';
762+
riskSpan.style.background = rc + '12';
763+
riskSpan.style.color = rc;
764+
riskSpan.textContent = threat.risk + ' ' + detailLabels.riskSuffix;
765+
badgesDiv.appendChild(riskSpan);
766+
var atlasLink = document.createElement('a');
767+
atlasLink.className = 'detail-badge atlas-link';
768+
atlasLink.style.background = 'rgba(255,90,54,0.08)';
769+
atlasLink.setAttribute('href', 'https://atlas.mitre.org/techniques/' + threat.atlas);
770+
atlasLink.setAttribute('target', '_blank');
771+
atlasLink.textContent = threat.atlas;
772+
badgesDiv.appendChild(atlasLink);
759773
document.getElementById('detailBody').innerHTML = '<div class="detail-section"><div class="detail-label">' + escapeHtml(detailLabels.description) + '</div><div class="detail-value">' + escapeHtml(threat.description) + '</div></div><div class="detail-section"><div class="detail-label">' + escapeHtml(detailLabels.attackVector) + '</div><div class="detail-value">' + escapeHtml(threat.attackVector) + '</div></div><div class="detail-section"><div class="detail-label">' + escapeHtml(detailLabels.affected) + '</div><div class="detail-value">' + escapeHtml(threat.affected) + '</div></div><div class="detail-section"><div class="detail-label">' + escapeHtml(detailLabels.mitigations) + '</div><div class="detail-value">' + escapeHtml(threat.mitigations) + '</div></div><div class="detail-section"><div class="detail-label">' + escapeHtml(detailLabels.residualRisk) + '</div><div class="detail-value">' + escapeHtml(threat.residualRisk) + '</div></div><div class="detail-section"><div class="detail-label">' + escapeHtml(detailLabels.recommendations) + '</div><div class="detail-value">' + escapeHtml(threat.recommendations) + '</div></div>';
760774
document.getElementById('detailOverlay').classList.add('active');
761775
document.querySelectorAll('.threat-card').forEach(function(c) { c.classList.remove('selected'); });

src/pages/trust/ko/threatmodel.astro

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -755,7 +755,21 @@ for (const b of trustBoundaries) {
755755
document.getElementById('detailId').textContent = threat.id + ' \u2022 ' + threat.tactic;
756756
document.getElementById('detailTitle').textContent = threat.name;
757757
var rc = getRiskColor(threat.risk);
758-
document.getElementById('detailBadges').innerHTML = '<span class="detail-badge" style="background:' + rc + '12;color:' + rc + '">' + escapeHtml(threat.risk) + ' ' + escapeHtml(detailLabels.riskSuffix) + '</span><a href="https://atlas.mitre.org/techniques/' + escapeHtml(threat.atlas) + '" target="_blank" class="detail-badge atlas-link" style="background:rgba(255,90,54,0.08)">' + escapeHtml(threat.atlas) + '</a>';
758+
var badgesDiv = document.getElementById('detailBadges');
759+
badgesDiv.innerHTML = '';
760+
var riskSpan = document.createElement('span');
761+
riskSpan.className = 'detail-badge';
762+
riskSpan.style.background = rc + '12';
763+
riskSpan.style.color = rc;
764+
riskSpan.textContent = threat.risk + ' ' + detailLabels.riskSuffix;
765+
badgesDiv.appendChild(riskSpan);
766+
var atlasLink = document.createElement('a');
767+
atlasLink.className = 'detail-badge atlas-link';
768+
atlasLink.style.background = 'rgba(255,90,54,0.08)';
769+
atlasLink.setAttribute('href', 'https://atlas.mitre.org/techniques/' + threat.atlas);
770+
atlasLink.setAttribute('target', '_blank');
771+
atlasLink.textContent = threat.atlas;
772+
badgesDiv.appendChild(atlasLink);
759773
document.getElementById('detailBody').innerHTML = '<div class="detail-section"><div class="detail-label">' + escapeHtml(detailLabels.description) + '</div><div class="detail-value">' + escapeHtml(threat.description) + '</div></div><div class="detail-section"><div class="detail-label">' + escapeHtml(detailLabels.attackVector) + '</div><div class="detail-value">' + escapeHtml(threat.attackVector) + '</div></div><div class="detail-section"><div class="detail-label">' + escapeHtml(detailLabels.affected) + '</div><div class="detail-value">' + escapeHtml(threat.affected) + '</div></div><div class="detail-section"><div class="detail-label">' + escapeHtml(detailLabels.mitigations) + '</div><div class="detail-value">' + escapeHtml(threat.mitigations) + '</div></div><div class="detail-section"><div class="detail-label">' + escapeHtml(detailLabels.residualRisk) + '</div><div class="detail-value">' + escapeHtml(threat.residualRisk) + '</div></div><div class="detail-section"><div class="detail-label">' + escapeHtml(detailLabels.recommendations) + '</div><div class="detail-value">' + escapeHtml(threat.recommendations) + '</div></div>';
760774
document.getElementById('detailOverlay').classList.add('active');
761775
document.querySelectorAll('.threat-card').forEach(function(c) { c.classList.remove('selected'); });

src/pages/trust/threatmodel.astro

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -754,7 +754,21 @@ for (const b of trustBoundaries) {
754754
document.getElementById('detailId').textContent = threat.id + ' \u2022 ' + threat.tactic;
755755
document.getElementById('detailTitle').textContent = threat.name;
756756
var rc = getRiskColor(threat.risk);
757-
document.getElementById('detailBadges').innerHTML = '<span class="detail-badge" style="background:' + rc + '12;color:' + rc + '">' + escapeHtml(threat.risk) + ' Risk</span><a href="https://atlas.mitre.org/techniques/' + escapeHtml(threat.atlas) + '" target="_blank" class="detail-badge atlas-link" style="background:rgba(255,90,54,0.08)">' + escapeHtml(threat.atlas) + '</a>';
757+
var badgesDiv = document.getElementById('detailBadges');
758+
badgesDiv.innerHTML = '';
759+
var riskSpan = document.createElement('span');
760+
riskSpan.className = 'detail-badge';
761+
riskSpan.style.background = rc + '12';
762+
riskSpan.style.color = rc;
763+
riskSpan.textContent = threat.risk + ' Risk';
764+
badgesDiv.appendChild(riskSpan);
765+
var atlasLink = document.createElement('a');
766+
atlasLink.className = 'detail-badge atlas-link';
767+
atlasLink.style.background = 'rgba(255,90,54,0.08)';
768+
atlasLink.setAttribute('href', 'https://atlas.mitre.org/techniques/' + threat.atlas);
769+
atlasLink.setAttribute('target', '_blank');
770+
atlasLink.textContent = threat.atlas;
771+
badgesDiv.appendChild(atlasLink);
758772
document.getElementById('detailBody').innerHTML = '<div class="detail-section"><div class="detail-label">Description</div><div class="detail-value">' + escapeHtml(threat.description) + '</div></div><div class="detail-section"><div class="detail-label">Attack Vector</div><div class="detail-value">' + escapeHtml(threat.attackVector) + '</div></div><div class="detail-section"><div class="detail-label">Affected Components</div><div class="detail-value">' + escapeHtml(threat.affected) + '</div></div><div class="detail-section"><div class="detail-label">Current Mitigations</div><div class="detail-value">' + escapeHtml(threat.mitigations) + '</div></div><div class="detail-section"><div class="detail-label">Residual Risk</div><div class="detail-value">' + escapeHtml(threat.residualRisk) + '</div></div><div class="detail-section"><div class="detail-label">Recommendations</div><div class="detail-value">' + escapeHtml(threat.recommendations) + '</div></div>';
759773
document.getElementById('detailOverlay').classList.add('active');
760774
document.querySelectorAll('.threat-card').forEach(function(c) { c.classList.remove('selected'); });

src/pages/trust/zh-cn/threatmodel.astro

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -755,7 +755,21 @@ for (const b of trustBoundaries) {
755755
document.getElementById('detailId').textContent = threat.id + ' \u2022 ' + threat.tactic;
756756
document.getElementById('detailTitle').textContent = threat.name;
757757
var rc = getRiskColor(threat.risk);
758-
document.getElementById('detailBadges').innerHTML = '<span class="detail-badge" style="background:' + rc + '12;color:' + rc + '">' + escapeHtml(threat.risk) + ' ' + escapeHtml(detailLabels.riskSuffix) + '</span><a href="https://atlas.mitre.org/techniques/' + escapeHtml(threat.atlas) + '" target="_blank" class="detail-badge atlas-link" style="background:rgba(255,90,54,0.08)">' + escapeHtml(threat.atlas) + '</a>';
758+
var badgesDiv = document.getElementById('detailBadges');
759+
badgesDiv.innerHTML = '';
760+
var riskSpan = document.createElement('span');
761+
riskSpan.className = 'detail-badge';
762+
riskSpan.style.background = rc + '12';
763+
riskSpan.style.color = rc;
764+
riskSpan.textContent = threat.risk + ' ' + detailLabels.riskSuffix;
765+
badgesDiv.appendChild(riskSpan);
766+
var atlasLink = document.createElement('a');
767+
atlasLink.className = 'detail-badge atlas-link';
768+
atlasLink.style.background = 'rgba(255,90,54,0.08)';
769+
atlasLink.setAttribute('href', 'https://atlas.mitre.org/techniques/' + threat.atlas);
770+
atlasLink.setAttribute('target', '_blank');
771+
atlasLink.textContent = threat.atlas;
772+
badgesDiv.appendChild(atlasLink);
759773
document.getElementById('detailBody').innerHTML = '<div class="detail-section"><div class="detail-label">' + escapeHtml(detailLabels.description) + '</div><div class="detail-value">' + escapeHtml(threat.description) + '</div></div><div class="detail-section"><div class="detail-label">' + escapeHtml(detailLabels.attackVector) + '</div><div class="detail-value">' + escapeHtml(threat.attackVector) + '</div></div><div class="detail-section"><div class="detail-label">' + escapeHtml(detailLabels.affected) + '</div><div class="detail-value">' + escapeHtml(threat.affected) + '</div></div><div class="detail-section"><div class="detail-label">' + escapeHtml(detailLabels.mitigations) + '</div><div class="detail-value">' + escapeHtml(threat.mitigations) + '</div></div><div class="detail-section"><div class="detail-label">' + escapeHtml(detailLabels.residualRisk) + '</div><div class="detail-value">' + escapeHtml(threat.residualRisk) + '</div></div><div class="detail-section"><div class="detail-label">' + escapeHtml(detailLabels.recommendations) + '</div><div class="detail-value">' + escapeHtml(threat.recommendations) + '</div></div>';
760774
document.getElementById('detailOverlay').classList.add('active');
761775
document.querySelectorAll('.threat-card').forEach(function(c) { c.classList.remove('selected'); });

0 commit comments

Comments
 (0)