discourse/plugins/discourse-graphviz/assets/javascripts/initializers/discourse-graphviz.js
Penar Musaraj 472f9e1f78 SECURITY: Sanitize graphviz SVG anchor links to prevent XSS
Graphviz can generate SVG output with anchor elements containing
arbitrary URLs specified by users. This change implements server-side
sanitization to allow only http and https URLs.
2026-03-19 15:21:28 +00:00

69 lines
1.8 KiB
JavaScript

/* eslint-disable ember/no-jquery */
import $ from "jquery";
import { escape } from "pretty-text/sanitizer";
import discourseDebounce from "discourse/lib/debounce";
import loadScript from "discourse/lib/load-script";
import { withPluginApi } from "discourse/lib/plugin-api";
export default {
name: "discourse-graphviz",
renderGraphs($containers) {
$containers.each((_, container) => {
const $container = $(container);
// if the container content has not yet been replaced
// do nothing
if (!$container.find("svg").length) {
this.renderGraph($container);
}
});
},
renderGraph($container) {
const graphDefinition = $container.text().trim();
const engine = $container.attr("data-engine");
const $spinner = $("<div class='spinner tiny'></div>");
$container.html($spinner);
loadScript("/plugins/discourse-graphviz/javascripts/viz-3.0.1.js").then(
() => {
$container.removeClass("is-loading");
try {
/* global vizRenderStringSync */
const svgChart = vizRenderStringSync(graphDefinition, {
format: "svg",
engine,
});
$container.html(svgChart);
} catch (e) {
const $error = $(
`<div class='graph-error'>${escape(e.message)}</div>`
);
$container.html($error);
}
}
);
},
initialize(container) {
const siteSettings = container.lookup("service:site-settings");
if (siteSettings.discourse_graphviz_enabled) {
withPluginApi((api) => {
api.decorateCooked(
($elem) => {
const $graphviz = $elem.find(".graphviz");
if ($graphviz.length) {
discourseDebounce(this, this.renderGraphs, $graphviz, 200);
}
},
{ id: "graphviz" }
);
});
}
},
};