VYPR
Medium severityNVD Advisory· Published Feb 13, 2026· Updated Apr 15, 2026

CVE-2026-26226

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.

PackageAffected versionsPatched versions
beautiful-mermaidnpm
< 0.1.30.1.3

Affected products

1

Patches

1
68f3ab8c9658

security: escape inline style values to prevent SVG attribute injection (#8)

https://github.com/lukilabs/beautiful-mermaidWill DollmanJan 29, 2026via ghsa
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 &lt; 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&quot; onmouseover=&quot;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('&lt;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&quot; onclick=&quot;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&quot; onmouseover=&quot;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&quot; onfocus=&quot;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

News mentions

0

No linked articles in our index yet.