Feb 14, 2026·6 min read·6 visits
The `beautiful-mermaid` library, used to render Mermaid diagrams as SVGs, failed to sanitize user-supplied style attributes. Attackers can inject malicious payloads into `fill` or `stroke` properties, breaking out of the SVG XML structure to execute XSS. Fixed in version 0.1.3.
A critical flaw in the `beautiful-mermaid` library allows attackers to weaponize flowcharts and diagrams. By injecting malicious CSS strings into style definitions, adversaries can break out of SVG attributes and execute arbitrary JavaScript. This vulnerability transforms a harmless documentation tool into a vehicle for Cross-Site Scripting (XSS), affecting all versions prior to 0.1.3.
Mermaid diagrams are the darling of the developer documentation world. They turn cryptic text into beautiful flowcharts, sequence diagrams, and Gantt charts. The beautiful-mermaid library exists to take that text and render it into high-fidelity SVGs, perfect for embedding in websites or documentation portals. It promises "beautiful" rendering, but under the hood, it was doing something ugly: trusting user input blindly.
Imagine you are building a documentation platform. You allow users to submit Mermaid graphs to explain their microservices architecture. You render these server-side or client-side using beautiful-mermaid. It seems harmless enough—after all, it's just boxes and arrows, right? Wrong. In the security world, if you are building HTML or XML strings by mashing variables together without sanitization, you aren't building a renderer; you're building a vulnerability.
CVE-2026-26226 is the classic tale of convenience over security. The library treated style attributes like color, fill, and stroke as pure aesthetic choices, never expecting that someone might try to paint a box with a shade of " onload="alert(1). This oversight turns every diagram into a potential landmine for Cross-Site Scripting (XSS).
The root cause of this vulnerability is a fundamental misunderstanding of how to safely construct XML (and by extension, SVG) documents. There are two ways to build a DOM element: you can use a DOM API (like document.createElement), which naturally handles encoding, or you can build a massive string and hope for the best. beautiful-mermaid chose the latter.
In src/renderer.ts, the library parses the Mermaid syntax to extract node styles. If a user defines a specific style for a node—say, making it red—the library captures that string. The fatal error occurred when the library took those captured strings and interpolated them directly into a template literal to form the SVG output.
It didn't care what was in the string. It didn't look for quotes, angle brackets, or escape characters. It just pasted the value directly into the fill="..." attribute of the SVG string. This is the XML equivalent of SQL injection. The browser parser doesn't know that the attacker's double-quote meant "end of the attribute value." It just sees a closed attribute and a new, malicious instruction following immediately after.
Let's look at the crime scene in src/renderer.ts. The code was responsible for taking style properties and applying them to the SVG elements. Here is the vulnerable logic before the patch:
// Vulnerable: Direct interpolation of user input
const fill = inlineStyle?.fill ?? 'var(--_node-fill)'
const stroke = inlineStyle?.stroke ?? 'var(--_node-stroke)'
const sw = inlineStyle?.['stroke-width'] ?? String(STROKE_WIDTHS.innerBox)
// Later, this is used in a template literal:
// return `<rect fill="${fill}" stroke="${stroke}" ... />`If inlineStyle.fill is controlled by the user, they control the raw HTML output. The fix, introduced in version 0.1.3, is a textbook application of output encoding. The developers wrapped the tainted variables in an escapeXml function before interpolation:
// Fixed: Sanitizing the input before interpolation
const fill = escapeXml(inlineStyle?.fill ?? 'var(--_node-fill)')
const stroke = escapeXml(inlineStyle?.stroke ?? 'var(--_node-stroke)')
const sw = escapeXml(inlineStyle?.['stroke-width'] ?? String(STROKE_WIDTHS.innerBox))This simple change ensures that a double quote " becomes ", preventing the attacker from breaking out of the attribute context. It converts a weaponized string back into a harmless (albeit likely invalid) color value.
Exploiting this is trivially easy once you understand the underlying string concatenation. We don't need complex memory corruption or race conditions; we just need to balance some quotes. The attack vector typically involves the style directive in Mermaid syntax, which allows applying CSS to specific nodes.
Scenario 1: The Event Handler Injection
We want to inject onmouseover="alert(1)". The target code looks roughly like <rect fill="${USER_INPUT}" ... />. If we set our fill color to red" onmouseover="alert(1), the rendered SVG becomes:
<rect fill="red" onmouseover="alert(1)" ... />The browser sees a red rectangle that executes JavaScript when you hover over it.
The Payload:
Scenario 2: The Element Injection
If we want to be louder, we can close the rect tag entirely and start a new script tag. We set the fill to red"/><script>alert(origin)</script><rect fill="x.
The Payload:
This results in a valid SVG structure that includes an inline script block, executing immediately upon rendering in many contexts (though modern CSPs might block inline scripts, the event handler vector remains potent).
While the CVSS score is a moderate 5.3 (primarily because user interaction is usually required to view the diagram, and the scope is limited to the browser), the practical impact can be severe depending on the implementation. This is a classic stored XSS vector.
If a developer uses beautiful-mermaid to render user-generated content (like comments, wiki pages, or issue trackers) server-side and serves the resulting SVG, they are serving weaponized content. SVGs are powerful; they are essentially XML documents that can contain scripts, external object references, and more.
Successful exploitation means an attacker can:
Since this library is likely used in documentation tools or dashboards, the victims are often privileged users (developers, admins) viewing internal diagrams, making the target value high.
The remediation is straightforward: stop trusting strings. The maintainers of beautiful-mermaid released version 0.1.3 which includes the necessary escaping logic. If you are using this library, you need to upgrade immediately.
> [!TIP]
> Action Item: Run npm list beautiful-mermaid or bun pm ls to check your version. If it is < 0.1.3, update it.
If you cannot update for some reason (perhaps you enjoy living on the edge), you must implement a strict Content Security Policy (CSP). A CSP that forbids unsafe-inline scripts and restricts object-src can mitigate the impact of the XSS, preventing the execution of the injected JavaScript even if the HTML is malformed.
However, the only true fix is the patch. Sanitization is hard; don't try to write your own regex to strip quotes. Use the vendor's fix which properly encodes XML entities.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:L/VI:L/VA:N/SC:L/SI:L/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
beautiful-mermaid lukilabs | < 0.1.3 | 0.1.3 |
| Attribute | Detail |
|---|---|
| CWE | CWE-79 (XSS) |
| CVSS v4.0 | 5.3 (Medium) |
| Attack Vector | Network (Payload delivery) |
| Privileges Required | None |
| User Interaction | Passive (Viewing diagram) |
| Exploit Status | PoC Available |
Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')