@@ -9,7 +9,7 @@ import DOMPurify from 'dompurify';
99import * as toolbox from "js/toolbox" ;
1010import { useEffect , useState } from "preact/hooks" ;
1111
12- Mermaid . initialize ( { startOnLoad : false , theme : `default` , securityLevel : `strict ` } ) ;
12+ Mermaid . initialize ( { startOnLoad : false , theme : `default` , securityLevel : `sandbox ` } ) ;
1313const 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