CVE-2026-26226
Description
beautiful-mermaid versions prior to 0.1.3 contain an SVG attribute injection issue that can lead to cross-site scripting (XSS) when rendering attacker-controlled Mermaid diagrams. User-controlled values from Mermaid style and classDef directives are interpolated into SVG attribute values without proper escaping, allowing crafted input to break out of an attribute context and inject arbitrary SVG elements/attributes into the rendered output. When the generated SVG is embedded in a web page, this can result in script execution in the context of the embedding origin.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
beautiful-mermaidnpm | < 0.1.3 | 0.1.3 |
Affected products
1Patches
168f3ab8c9658security: escape inline style values to prevent SVG attribute injection (#8)
2 files changed · +44 −4
src/renderer.ts+4 −4 modified@@ -227,9 +227,9 @@ function renderNodeShape(node: PositionedNode): string { // Resolve fill and stroke — inline styles (from mermaid `style` directives) // override the CSS variable defaults. When no inline style is present, the // CSS variable handles theming automatically via color-mix() derivation. - const fill = inlineStyle?.fill ?? 'var(--_node-fill)' - const stroke = inlineStyle?.stroke ?? 'var(--_node-stroke)' - const sw = inlineStyle?.['stroke-width'] ?? String(STROKE_WIDTHS.innerBox) + 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)) switch (shape) { case 'diamond': @@ -461,7 +461,7 @@ function renderNodeLabel(node: PositionedNode, font: string): string { const cy = node.y + node.height / 2 // Resolve text color — inline styles can override the CSS variable default - const textColor = node.inlineStyle?.color ?? 'var(--_text)' + const textColor = escapeXml(node.inlineStyle?.color ?? 'var(--_text)') return ( `<text x="${cx}" y="${cy}" text-anchor="middle" dy="${TEXT_BASELINE_SHIFT}" ` +
src/__tests__/renderer.test.ts+40 −0 modified@@ -462,6 +462,46 @@ describe('renderSvg – XML escaping', () => { const svg = renderSvg(graph, lightColors) expect(svg).toContain('A < B') }) + + it('escapes attribute injection in inline style fill', () => { + const node = makeNode({ inlineStyle: { fill: 'red" onmouseover="alert(1)' } }) + const graph = makeGraph({ nodes: [node] }) + const svg = renderSvg(graph, lightColors) + expect(svg).not.toContain('onmouseover="alert') + expect(svg).toContain('red" onmouseover="alert(1)') + }) + + it('escapes element injection in inline style fill', () => { + const node = makeNode({ inlineStyle: { fill: 'red"/><svg onload="alert(1)"><rect fill="x' } }) + const graph = makeGraph({ nodes: [node] }) + const svg = renderSvg(graph, lightColors) + expect(svg).not.toContain('<svg onload') + expect(svg).toContain('<svg onload=') + }) + + it('escapes injection in inline style stroke', () => { + const node = makeNode({ inlineStyle: { stroke: 'blue" onclick="alert(1)' } }) + const graph = makeGraph({ nodes: [node] }) + const svg = renderSvg(graph, lightColors) + expect(svg).not.toContain('onclick="alert') + expect(svg).toContain('blue" onclick="alert(1)') + }) + + it('escapes injection in inline style stroke-width', () => { + const node = makeNode({ inlineStyle: { 'stroke-width': '2" onmouseover="alert(1)' } }) + const graph = makeGraph({ nodes: [node] }) + const svg = renderSvg(graph, lightColors) + expect(svg).not.toContain('onmouseover="alert') + expect(svg).toContain('2" onmouseover="alert(1)') + }) + + it('escapes injection in inline style color', () => { + const node = makeNode({ inlineStyle: { color: 'green" onfocus="alert(1)' } }) + const graph = makeGraph({ nodes: [node] }) + const svg = renderSvg(graph, lightColors) + expect(svg).not.toContain('onfocus="alert') + expect(svg).toContain('green" onfocus="alert(1)') + }) }) // ============================================================================
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-cgmm-x5ww-q5crghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-26226ghsaADVISORY
- github.com/lukilabs/beautiful-mermaid/commit/68f3ab8c9658e7f4a3b749e06a6b96e4c3f55db1ghsaWEB
- github.com/lukilabs/beautiful-mermaid/pull/8nvdWEB
- github.com/lukilabs/beautiful-mermaid/releases/tag/v0.1.3nvdWEB
- neo.projectdiscovery.io/share/cec71dc7-a8eb-417e-b8b4-666644796c1envdWEB
- www.vulncheck.com/advisories/beautiful-mermaid-svg-attribute-injectionnvdWEB
News mentions
0No linked articles in our index yet.