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},