Skip to content
This repository was archived by the owner on Jun 18, 2026. It is now read-only.

Commit 4328537

Browse files
security: HTML-escape user-controlled vertex names in HTML reports (CWE-79)
GraphHealthChecker.toHtml() injected vertex names (isolatedNodes, degreeOutliers) from graph files directly into HTML via truncateList() without escaping. A malicious graph file with vertex names containing HTML/JS (e.g. '<img/src=x onerror=alert(1)>') would execute arbitrary scripts when the health report was opened in a browser. Fix: wrap truncateList() output with ExportUtils.escapeHtml() for both isolated-node and degree-outlier findings. GraphDiffHtmlExporter used D3's .html() (which parses HTML) with raw vertex IDs in tooltip template literals. Added a client-side esc() helper that uses textContent/innerHTML to sanitize vertex names before HTML insertion.
1 parent 128f768 commit 4328537

2 files changed

Lines changed: 4 additions & 3 deletions

File tree

Gvisual/src/gvisual/GraphDiffHtmlExporter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ private void appendData(StringBuilder sb, GraphDiffAnalyzer.DiffResult diff) {
236236
private void appendJs(StringBuilder sb) {
237237
sb.append(
238238
"const colors = { added: '#00e676', removed: '#ff5252', common: '#78909c' };\n" +
239+
"function esc(s){var d=document.createElement('span');d.textContent=s;return d.innerHTML;}\n" +
239240
"const tooltip = d3.select('#tooltip');\n" +
240241
"const container = d3.select('#graph');\n" +
241242
"const w = container.node().clientWidth;\n" +
@@ -294,7 +295,7 @@ private void appendJs(StringBuilder sb) {
294295
" .call(d3.drag().on('start', dragStart).on('drag', dragging).on('end', dragEnd))\n" +
295296
" .on('mouseover', (e, d) => {\n" +
296297
" tooltip.style('display', 'block')\n" +
297-
" .html(`<b>${d.id}</b><br>Status: ${d.status}<br>Degree in ${diffData.labelA}: ${d.degA}<br>Degree in ${diffData.labelB}: ${d.degB}`)\n" +
298+
" .html(`<b>${esc(d.id)}</b><br>Status: ${d.status}<br>Degree in ${esc(diffData.labelA)}: ${d.degA}<br>Degree in ${esc(diffData.labelB)}: ${d.degB}`)\n" +
298299
" .style('left', (e.pageX + 12) + 'px').style('top', (e.pageY - 10) + 'px');\n" +
299300
" })\n" +
300301
" .on('mouseout', () => tooltip.style('display', 'none'))\n" +

Gvisual/src/gvisual/GraphHealthChecker.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ public static String toHtml(HealthReport r) {
183183
sb.append("<div class='issue ok'>✅ No isolated nodes</div>");
184184
} else {
185185
sb.append(String.format("<div class='issue'>⚠️ %d isolated node(s): %s</div>",
186-
r.isolatedNodes.size(), truncateList(r.isolatedNodes, 20)));
186+
r.isolatedNodes.size(), ExportUtils.escapeHtml(truncateList(r.isolatedNodes, 20))));
187187
}
188188
if (r.selfLoops.isEmpty()) {
189189
sb.append("<div class='issue ok'>✅ No self-loops</div>");
@@ -211,7 +211,7 @@ public static String toHtml(HealthReport r) {
211211
sb.append("<div class='issue ok'>✅ No degree outliers</div>");
212212
} else {
213213
sb.append(String.format("<div class='issue'>⚠️ %d degree outlier(s): %s</div>",
214-
r.degreeOutliers.size(), truncateList(r.degreeOutliers, 15)));
214+
r.degreeOutliers.size(), ExportUtils.escapeHtml(truncateList(r.degreeOutliers, 15))));
215215
}
216216

217217
sb.append("</body></html>");

0 commit comments

Comments
 (0)