From bb2049cec1790dae9d4b5cfcfc56627904e96f1f Mon Sep 17 00:00:00 2001 From: Safi Date: Thu, 9 Apr 2026 09:06:02 +0100 Subject: [PATCH] Fix XSS in HTML viz: escape node labels, types, source files, and edge relations in innerHTML (#sec) Co-Authored-By: Claude Sonnet 4.6 --- graphify/export.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/graphify/export.py b/graphify/export.py index 300f93ace..16271a730 100644 --- a/graphify/export.py +++ b/graphify/export.py @@ -110,6 +110,11 @@ const RAW_NODES = {nodes_json}; const RAW_EDGES = {edges_json}; const LEGEND = {legend_json}; +// HTML-escape helper — prevents XSS when injecting graph data into innerHTML +function esc(s) {{ + return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"').replace(/'/g,'''); +}} + // Build vis datasets const nodesDS = new vis.DataSet(RAW_NODES.map(n => ({{ id: n.id, label: n.label, color: n.color, size: n.size, @@ -165,13 +170,13 @@ function showInfo(nodeId) {{ const neighborItems = neighborIds.map(nid => {{ const nb = nodesDS.get(nid); const color = nb ? nb.color.background : '#555'; - return `${{nb ? nb.label : nid}}`; + return `${{esc(nb ? nb.label : nid)}}`; }}).join(''); document.getElementById('info-content').innerHTML = ` -
${{n.label}}
-
Type: ${{n._file_type || 'unknown'}}
-
Community: ${{n._community_name}}
-
Source: ${{n._source_file || '-'}}
+
${{esc(n.label)}}
+
Type: ${{esc(n._file_type || 'unknown')}}
+
Community: ${{esc(n._community_name)}}
+
Source: ${{esc(n._source_file || '-')}}
Degree: ${{n._degree}}
${{neighborIds.length ? `
Neighbors (${{neighborIds.length}})
${{neighborItems}}
` : ''}} `; @@ -356,7 +361,7 @@ def to_html( "color": {"background": color, "border": color, "highlight": {"background": "#ffffff", "border": color}}, "size": round(size, 1), "font": {"size": font_size, "color": "#ffffff"}, - "title": f"{label}", + "title": _html.escape(label), "community": cid, "community_name": sanitize_label((community_labels or {}).get(cid, f"Community {cid}")), "source_file": sanitize_label(data.get("source_file", "")), @@ -373,7 +378,7 @@ def to_html( "from": u, "to": v, "label": relation, - "title": f"{relation} [{confidence}]", + "title": _html.escape(f"{relation} [{confidence}]"), "dashes": confidence != "EXTRACTED", "width": 2 if confidence == "EXTRACTED" else 1, "color": {"opacity": 0.7 if confidence == "EXTRACTED" else 0.35},