Skip to content

Commit a0b8383

Browse files
fix(front): Enable mermaid rendering on reports tab (#618)
## 📝 Description Introduces some tests for reports tab, allows class attribute so `.mermaid` class is preserved and `mermaid-js` can render elements. `mermaid` set to render into a `iframe` to isolate rendered charts context. ## ✅ Checklist - [x] I have tested this change - [ ] This change requires documentation update
1 parent d23d275 commit a0b8383

File tree

5 files changed

+344
-107
lines changed

5 files changed

+344
-107
lines changed

front/assets/js/report/index.tsx

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import DOMPurify from 'dompurify';
99
import * as toolbox from "js/toolbox";
1010
import { useEffect, useState } from "preact/hooks";
1111

12-
Mermaid.initialize({ startOnLoad: false, theme: `default`, securityLevel: `strict` });
12+
Mermaid.initialize({ startOnLoad: false, theme: `default`, securityLevel: `sandbox` });
1313
const md = MarkdownIt({
1414
html: true,
1515
linkify: false,
@@ -83,6 +83,21 @@ const MarkdownBody = (props: { markdown: string, }) => {
8383
}, [props.markdown]);
8484

8585
const renderedHtml = md.render(props.markdown);
86+
// Note: Sanitization is applied here before mermaid rendering. Some mermaid-specific tags
87+
// (like those generated by markdown-it-textual-uml) are intentionally not included in ALLOWED_TAGS
88+
// because they're processed by Mermaid.run() after sanitization.
89+
// Additionally, Mermaid provides two more security layers:
90+
// 1. It uses DOMPurify internally for sanitizing diagram content
91+
// 2. With securityLevel: 'sandbox' configured above, it renders each diagram in a sandboxed iframe
92+
// First, configure DOMPurify hooks
93+
DOMPurify.addHook(`afterSanitizeAttributes`, function(node) {
94+
// Force all links to open in new tab with security attributes
95+
if (`tagName` in node && node.tagName === `A`) {
96+
node.setAttribute(`target`, `_blank`);
97+
node.setAttribute(`rel`, `noopener noreferrer nofollow`);
98+
}
99+
});
100+
86101
const sanitizedHtml = DOMPurify.sanitize(renderedHtml, {
87102
ALLOWED_TAGS: [
88103
// Basic
@@ -98,17 +113,29 @@ const MarkdownBody = (props: { markdown: string, }) => {
98113
`mark`,
99114
`ins`,
100115
`small`,
101-
`abbr`
116+
`abbr`,
117+
// Links (safe with DOMPurify's URL sanitization)
118+
`a`
102119
],
103120
ALLOWED_ATTR: [
104121
`title`,
105-
`open`
122+
`open`,
123+
`class`,
124+
`href`, // DOMPurify by default blocks dangerous protocols (javascript:, data:, vbscript:) and only allows safe ones (http:, https:, mailto:, etc.)
125+
`target`,
126+
`rel`
106127
],
107-
FORBID_TAGS: [`a`, `img`, `script`, `object`, `embed`, `iframe`, `link`],
108-
FORBID_ATTR: [`href`, `src`, `class`, `id`, `style`, `target`],
109-
ALLOW_DATA_ATTR: false
128+
// Critical: Keep blocking dangerous tags that were part of the original vulnerability
129+
FORBID_TAGS: [`img`, `script`, `object`, `embed`, `iframe`, `link`, `form`, `input`, `style`, `meta`, `base`],
130+
FORBID_ATTR: [`src`, `id`, `style`, `onclick`, `onload`, `onerror`, `action`, `method`],
131+
ALLOW_DATA_ATTR: false,
132+
// Force secure link attributes
133+
ADD_ATTR: [`target`, `rel`]
110134
});
111135

136+
// Clean up the hook after sanitization to avoid memory leaks
137+
DOMPurify.removeHook(`afterSanitizeAttributes`);
138+
112139
return (
113140
<div
114141
className="markdown-body"

0 commit comments

Comments
 (0)