fix(frontend): set Mermaid securityLevel to strict to prevent XSS#898
Open
sebastiondev wants to merge 1 commit into
Open
fix(frontend): set Mermaid securityLevel to strict to prevent XSS#898sebastiondev wants to merge 1 commit into
sebastiondev wants to merge 1 commit into
Conversation
The Mermaid renderer's per-render initialize() call was overriding the top-level 'strict' setting with 'loose'. With securityLevel: 'loose', Mermaid does not sanitize scripting/interactive constructs in the generated SVG, and the SVG is then injected into the DOM via dangerouslySetInnerHTML — allowing XSS from any attacker-controllable markdown source (chat messages, task descriptions, etc.). Aligns the runtime re-initialization with the module-level default.
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.
Summary
The Markdown renderer in
frontend/src/components/common/markdown.tsxinitialises Mermaid withsecurityLevel: "loose". In that mode Mermaid does not sanitize label/node text and allows raw HTML (including<script>,<img onerror=...>,javascript:links, click-bindings, etc.) to pass through into the rendered SVG. That SVG is then injected into the DOM viadangerouslySetInnerHTML, so any script content in a Mermaid diagram executes in the victim's browser origin.The markdown pipeline uses
rehypeSanitizefor regular HTML, but Mermaid diagrams are handled by a separate custom code-block renderer that bypasses that sanitizer entirely.securityLevel: "loose"is therefore the only gate, and it is disabled.frontend/src/components/common/markdown.tsx(Mermaid init, line ~44)Fix
One-line change: switch Mermaid's
securityLevelfrom"loose"to"strict".In
"strict"mode, Mermaid HTML-escapes user text in labels and disables click/callback bindings, which is the vendor-recommended setting for rendering untrusted diagram source. This is the minimal, targeted mitigation and does not affect diagram layout, theming, or any legitimate Mermaid syntax — only inline HTML/JS injected into labels is neutralised.Proof of Concept
With the pre-patch build, render any markdown containing the following fenced block (via a shared task description, chat message, or LLM response):
Before the fix: on
loose, Mermaid emits the<img>unescaped inside the SVG<foreignObject>, theonerrorfires when the image fails to load, andalert(document.domain)executes in the MonkeyCode origin. Session cookies, task data, and any authenticated API accessible to the victim are then reachable to attacker-controlled JS.After the fix: on
strict, Mermaid HTML-escapes the label text; the<img>renders as literal text inside the diagram node and no script executes.Mermaid's own security documentation describes the same class of issue: https://mermaid.js.org/config/usage.html#securitylevel
What we tested
Mermaidcomponent is the only markdown path that bypassesrehypeSanitize— the rest of the pipeline routes HTML throughrehypeSanitize.dangerouslySetInnerHTML={{ __html: svg }}in the same file (line ~73), so anything Mermaid emits reaches the DOM as HTML.Adversarial review
Before submitting, we tried to disprove the finding. Two things could have made it a non-issue: (1) an upstream sanitizer between Mermaid's SVG and the DOM, or (2) Mermaid handling HTML safely even in
loosemode. Neither holds —rehypeSanitizeis applied only to the HAST tree, not to the raw SVG string produced bymermaid.render(), and Mermaid documents thatlooseexplicitly permits HTML in labels. We also considered whether the markdown source is always trusted; in a shared-workspace AI product, task/chat/LLM output is routinely rendered cross-user, so the untrusted-input precondition is satisfied by normal product usage.Discovered by the Sebastion AI GitHub App.