fix(mermaid): inject SVG via HTML parser, not strict XML#54
Open
thomnico wants to merge 1 commit into
Open
Conversation
Mermaid 10+ embeds HTML inside <foreignObject> for multi-line node labels
(e.g. <p>line one<br>line two</p>). The unclosed <br> is valid HTML but
not valid XML, which silently breaks two seemingly-safe injection paths:
- Element.innerHTML = svg
Works in browsers, but flagged as a potential XSS sink by
Semgrep / CSP-aware linters in many user environments. Agents
operating under those scanners reach for DOMParser as a workaround.
- DOMParser.parseFromString(svg, 'image/svg+xml') + adoptNode
Passes XSS scanners but the strict XML parser stops parsing at the
first <br> without throwing. The result is a truncated SVG: only
the first node renders, every edge disappears, no error in the
console of most browsers (Chrome surfaces it only as
"Opening and ending tag mismatch: br line 1 and p" via the page
error overlay, not the dev console).
Switch the canonical render() in templates/mermaid-flowchart.html to
DOMParser.parseFromString(svg, 'text/html') + adoptNode. The HTML5
parser is lenient about <br> and preserves the SVG namespace via
foreign-content rules, giving us a path that:
1. Passes XSS scanners (no innerHTML write).
2. Renders complex Mermaid diagrams correctly.
3. Surfaces real errors instead of silently truncating.
Document the trade-offs and the failure mode in css-patterns.md so
future skill maintainers (and agents that re-implement render() under
stricter security policies) don't fall back into the XML-parser trap.
Verified with a real-world plan-review diagram (15 nodes, 17 edges,
multi-line labels): rendered before fix = 1 node / 0 edges; after
fix = 15 nodes / 17 edges.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
fix(mermaid): inject SVG via HTML parser, not strict XML
TL;DR
Switch
templates/mermaid-flowchart.htmlfrom setting the canvas's HTMLproperty to a
DOMParserthat parses the SVG astext/html(HTML5,lenient) instead of
image/svg+xml(strict XML). Document the trap inreferences/css-patterns.md.Why
Mermaid 10+ emits HTML inside
<foreignObject>for multi-line node labels:The unclosed
<br>is valid HTML but not valid XML. That distinctionsilently breaks two patterns the skill — and agents reusing the skill under
stricter security policies — naturally reach for:
parseFromString(svg, 'image/svg+xml')+adoptNode<br>, silently truncates the SVGparseFromString(svg, 'text/html')+adoptNode<br>and preserves the SVG namespace via foreign-content rulesThe XML-parser failure mode is the dangerous one because it does not
throw — only the first node renders, every edge disappears, and the
browser console is empty. The underlying error surfaces only via Chrome's
page error overlay:
Opening and ending tag mismatch: br line 1 and p.Repro (before fix)
A real-world plan-review diagram I generated under a Semgrep policy that
blocks the canonical injection pattern:
Changes
plugins/visual-explainer/templates/mermaid-flowchart.htmlrender()withDOMParsertext/html+adoptNodeplugins/visual-explainer/references/css-patterns.mdrender()example to use the same patternwith the comparison table, failure mode, and a canonical
injectSvg(host, svgMarkup)helperCompatibility
DOMParserwithtext/htmlparsing —i.e. every browser that supports Mermaid 10+ in the first place.
parsed SVG is identical.
usable in security-hardened environments.
Out of scope
openInNewTabhandler still serialises via a template literal;that string is then parsed by the browser when the new tab loads, so it
does not hit the same trap. Left as-is to keep the diff minimal.