diff --git a/src/core/operations/SeriesChart.mjs b/src/core/operations/SeriesChart.mjs
index 7baf594c..3725441b 100644
--- a/src/core/operations/SeriesChart.mjs
+++ b/src/core/operations/SeriesChart.mjs
@@ -15,6 +15,20 @@ import Utils from "../Utils.mjs";
const d3 = d3temp.default ? d3temp.default : d3temp;
const nodom = nodomtemp.default ? nodomtemp.default: nodomtemp;
+/**
+ * Removes D3's internal bound data from a nodom tree before serialization.
+ * nodom serializes enumerable expando properties such as __data__ as attributes,
+ * so leaving them on attacker-controlled values can create executable markup.
+ *
+ * @param {Object} node
+ */
+function clearD3BoundData(node) {
+ delete node.__data__;
+
+ if (!node.childNodes) return;
+ node.childNodes.forEach(clearD3BoundData);
+}
+
/**
* Series chart operation
*/
@@ -222,6 +236,8 @@ class SeriesChart extends Operation {
.text(serie.name);
});
+ clearD3BoundData(svg.node());
+
return svg._groups[0][0].outerHTML;
}
diff --git a/tests/operations/tests/Charts.mjs b/tests/operations/tests/Charts.mjs
index a62b5741..6d56c732 100644
--- a/tests/operations/tests/Charts.mjs
+++ b/tests/operations/tests/Charts.mjs
@@ -41,6 +41,17 @@ TestRegister.addTests([
}
],
},
+ {
+ name: "Series chart escapes x-axis values in serialized SVG",
+ input: `s,x">