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

Commit d9d9548

Browse files
security: fix XSS in GraphTimelineExporter SVG innerHTML rendering
Vertex IDs were interpolated directly into SVG innerHTML via template literals in the client-side draw() function without HTML escaping. A vertex name containing '<script>' or event handler attributes (e.g. '<img onerror=alert(1)>') would execute arbitrary JavaScript when the timeline HTML file was opened in a browser. Added escH() HTML-entity encoder and applied it to node ID strings in both <title> tooltips and <text> labels before innerHTML insertion.
1 parent c88ee6b commit d9d9548

1 file changed

Lines changed: 4 additions & 2 deletions

File tree

Gvisual/src/gvisual/GraphTimelineExporter.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,15 +354,17 @@ private String getJS() {
354354
+ " const c = EDGE_COLORS[l.type] || EDGE_COLORS.unknown;\n"
355355
+ " html += `<line x1='${s.x}' y1='${s.y}' x2='${t.x}' y2='${t.y}' stroke='${c}' stroke-width='1.5' stroke-opacity='0.6'/>`;\n"
356356
+ " });\n"
357+
+ " function escH(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\"/g,'&quot;').replace(/'/g,'&#39;');}\n"
357358
+ " // Nodes\n"
358359
+ " nodes.forEach(n => {\n"
359360
+ " const r = Math.max(4, Math.min(16, 3 + n.degree));\n"
360361
+ " const cls = n.isNew ? 'node-enter' : '';\n"
361362
+ " const fill = n.isNew ? '#45e980' : '#e0e0e0';\n"
363+
+ " const safeId = escH(n.id);\n"
362364
+ " html += `<circle cx='${n.x}' cy='${n.y}' r='${r}' fill='${fill}' class='${cls}' stroke='#333' stroke-width='1'>`;\n"
363-
+ " html += `<title>${n.id} (degree: ${n.degree})</title></circle>`;\n"
365+
+ " html += `<title>${safeId} (degree: ${n.degree})</title></circle>`;\n"
364366
+ " if (nodes.length < 40) {\n"
365-
+ " html += `<text x='${n.x}' y='${n.y - r - 3}' text-anchor='middle' font-size='10' fill='#aaa'>${n.id}</text>`;\n"
367+
+ " html += `<text x='${n.x}' y='${n.y - r - 3}' text-anchor='middle' font-size='10' fill='#aaa'>${safeId}</text>`;\n"
366368
+ " }\n"
367369
+ " });\n"
368370
+ " svg.innerHTML = html;\n"

0 commit comments

Comments
 (0)